Game/Menu/MainMenu.razor

Razor UI component for the game's main menu. Renders the main menu layout, overlays (pet unlock, evolution, welcome gift), animated background and many tabbed panels like Community, Leaderboard, Stats, Shop, Farm and handles UI interactions (buttons, toggles, buy/open actions) by calling component methods and accessing game state/save data.

File AccessNetworking
@using System;
@using Sandbox;
@using Sandbox.Services;
@using Sandbox.UI;
@using Sandbox.Utility
@inherits PanelComponent

<root>
        @* ============================================================ *@
        @* ================ FIRST RUN PET UNLOCK POPUP ================ *@
        @* ============================================================ *@
   

        @if(_showPetUnlock)
        {
            <div class="mm-pet-unlock-overlay">
                <div class="mm-pet-unlock-popup">
                    <div class="mm-pet-unlock-eyebrow">A wild companion appeared!</div>
                    <image src="ui/icons/egg/egg_1.png" class="mm-pet-unlock-egg"/>
                    <div class="mm-pet-unlock-title">A mysterious egg</div>
                    <div class="mm-pet-unlock-desc">
                        It appeared after your third run.<br/>
                        Play more to hatch it.
                    </div>
                    <button class="mm-gift-close" style="pointer-events:all; margin-top:24px;" 
                        onclick=@OnClosePetUnlock>
                        Meet my pet ▶
                    </button>
                </div>
            </div>
        }

        @* ============================================================ *@
        @* =============== EVOLUTION CINEMATIC OVERLAY ================ *@
        @* ============================================================ *@
        @if(_showEvolution)
        {
            <div class="mm-evo-bg @(_evoPhase)">

                @* Dramatic text at top *@
                @if(!string.IsNullOrEmpty(_evoText))
                {
                    <div class="mm-evo-text @(_evoPhase)">@_evoText</div>
                }

                @* Pet — centered *@
                <div class="mm-evo-pet @(_evoPhase)">
                    @if(_petUnlockChestPhase)
                    {
                        @("📦")
                    }
                    else if(_evoShowNew)
                    {
                        <image [email protected](_save?.PetLevel ?? 1, _petFrameToggle)/>
                    }
                    else
                    {
                        <image [email protected](_evoFromLevel, _petFrameToggle)/>
                    }
                </div>

                @* Flash overlay *@
                @if(_evoPhase == "evo-flash")
                {
                    <div class="mm-evo-flash"></div>
                }

                @* Reveal buttons *@
                @if(_evoPhase == "evo-reveal")
                {
                    <div class="mm-evo-name">@GetPetName()</div>
                    <div class="mm-evo-level">Level @(_save?.PetLevel ?? 1)</div>
                    <button class="mm-gift-close"
                            style="pointer-events:all; margin-top:24px;"
                            onclick=@OnCloseEvolution>
                        Amazing! ▶
                    </button>
                }
            </div>
        }
        @* ============================================================ *@
        @* ==================== WELCOME GIFT OVERLAY ================== *@
        @* ============================================================ *@

        @if(_showWelcomeGift)
        {
            <div class="mm-welcome-bg"></div>

            @* Confetti — full screen, loops until dismissed *@
            <div class="mm-confetti-wrap">
                @for(int i = 0; i < 30; i++)
                {
                    float left  = (i * 3.7f) % 100f;
                    float dur   = 2.0f + (i * 0.15f) % 1.5f;
                    float delay = (i * 0.13f) % 2.0f;
                    int   size  = (i % 3 == 0) ? 10 : (i % 2 == 0) ? 7 : 5;
                    string color = (i % 5 == 0) ? "rgba(251,191,36,0.95)"
                                : (i % 4 == 0) ? "rgba(167,139,250,0.95)"
                                : (i % 3 == 0) ? "rgba(52,211,153,0.95)"
                                : (i % 2 == 0) ? "rgba(244,114,182,0.95)"
                                :                "rgba(96,165,250,0.95)";
                    float rot = (i * 23f) % 360f;
                    <div class="mm-confetti-piece"
                        style="left:@left.ToString("F1")%;
                                width:@(size)px;height:@(size)px;
                                background-color:@color;
                                animation-duration:@dur.ToString("F1")s;
                                animation-delay:@delay.ToString("F1")s;
                                transform:rotate(@rot.ToString("F0")deg);">
                    </div>
                }
            </div>

        <div class="mm-welcome-bg"></div>
        <div class="mm-welcome-gift">
            <div class="mm-gift-title">🎉 WELCOME!</div>
            <div class="mm-gift-subtitle">Open your starter gifts</div>
            
            <div class="mm-gift-chests">
                @for(int i = 0; i < _welcomeGifts.Count; i++)
                {
                    int idx = i;
                    var gift = _welcomeGifts[idx];
                    bool canOpen = !gift.Opened && idx == _giftStage;
                    
                    <div class="mm-gift-chest @(gift.Opened ? "opened" : "") @(canOpen ? "active" : "")"
                         onclick=@(() => { if(canOpen) OnOpenChest(idx); })>

                        @* What's shown when gift not opened yet *@
                        @if(!gift.Opened)
                        {
                            <div class="mm-chest-icon">🎁</div>
                            <div class="mm-chest-label">@(canOpen ? "CLICK!" : "")</div>
                        }
                        @* Show gift after click *@
                        else
                        {
                            <div class="mm-gift-icon">@gift.GiftIcon</div>
                            <div class="mm-gift-name">@gift.Title</div>
                            <div class="mm-gift-desc">@gift.Desc</div>
                        }
                    </div>
                }
            </div>
            
            @if(_giftStage >= _welcomeGifts.Count)
            {
                <button class="mm-gift-close" onclick=@OnCloseWelcome>
                    Let's play! ▶
                </button>
            }
        </div>
        }

        @* ============================================================================ *@
        @* ============================= SURVIVAL LOADOUT ============================= *@
        @* ============================================================================ *@

        @if(_showSurvivalLoadout)
        {
            <div class="mm-welcome-bg"></div>
            <div class="mm-welcome-gift survival-loadout-card">
                <div class="mm-gift-title" style="font-size:28px;">⚡ SURVIVAL LOADOUT</div>
                <div class="mm-gift-subtitle" style="margin-bottom:4px;">Chests never stop coming. Choose your bombs wisely.</div>
                <div class="survival-loadout-hint">Pick 1–3 bomb types · @_survivalSelectedBombs.Count/3 selected</div>

                <div class="survival-bomb-grid">
                    @foreach (var bomb in BombCatalog.All.Where(b => _save?.OwnsBomb(b.Type) ?? false)
                                                        .Prepend(new BombShopItem { Type = GridManager.BombType.Cross, Name = "Cross", Icon = "🎯", Hint = "+ shape, 1 tile range" }))
                    {
                        bool selected = _survivalSelectedBombs.Contains(bomb.Type);
                        <div class="survival-bomb-card @(selected ? "selected" : "")" 
                            onclick=@(() => ToggleSurvivalBomb(bomb.Type))>
                            @if(selected)
                            {
                                <div class="survival-bomb-check">✓</div>
                            }
                            <div class="survival-bomb-icon">@bomb.Icon</div>
                            <div class="survival-bomb-name">@bomb.Name</div>
                            <div class="survival-bomb-hint">@bomb.Hint</div>
                        </div>
                    }
                </div>

                @if (ShowsClashWarning())
                {
                    <div class="survival-tip-box">
                        <span class="survival-tip-icon">💡</span>
                        <span class="survival-tip-text">Diagonal + Cross can be awkward to combo — sticking to one type often makes cleaner chains.</span>
                    </div>
                }

                <button class="mm-gift-close survival-start-btn" 
                        disabled="@(_survivalSelectedBombs.Count == 0)"
                        onclick=@ConfirmSurvivalLoadoutAndStart>
                    ▶ Start Survival
                </button>
            </div>
        }

    @* ----------------------------------------------------------------- *@
    @* --------------------------- MAIN MENU --------------------------- *@
    @* ----------------------------------------------------------------- *@

    @if( !_gameStarted )
    {
        <div class="mm-bg">
            @* ── ANIMATED BACKGROUND ── *@
            <div class="mm-grid-bg">
                @for(int i = 0; i < 120; i++)
                {
                    int col  = i % 13;
                    int row  = i / 13;
                    float cx = 6f;
                    float cy = 4.5f;
                    float dist = MathF.Sqrt((col - cx) * (col - cx) + (row - cy) * (row - cy));

                    @* Pick cell type — bomb islands + chain connectors + idle fill *@
                    string cls = "mm-gc-idle";
                    if      (i == 21)  cls = "mm-gc-expl2";
                    else if (i == 59)  cls = "mm-gc-expl";
                    else if (i == 13 || i == 28 || i == 42 || i == 55 || i == 80 || i == 93 || i == 107) cls = "mm-gc-purp";
                    else if (i == 8  || i == 19 || i == 44 || i == 57 || i == 83 || i == 96 || i == 112) cls = "mm-gc-gold";
                    else if (i == 2  || i == 15 || i == 39 || i == 52 || i == 65 || i == 91 || i == 117) cls = "mm-gc-red";
                    else if (i == 14 || i == 27 || i == 32 || i == 33 || i == 34 || i == 40 ||
                            i == 45 || i == 46 || i == 47 || i == 53 || i == 56 || i == 58 ||
                            i == 66 || i == 71 || i == 72 || i == 73 || i == 75 || i == 84 ||
                            i == 85 || i == 86 || i == 87 || i == 88)  cls = "mm-gc-chain";

                    @* Per-type animation period for phase-correct delay *@
                    float period = cls switch {
                        "mm-gc-expl"  => 7f,
                        "mm-gc-expl2" => 9f,
                        "mm-gc-purp"  => 4.5f,
                        "mm-gc-gold"  => 5f,
                        "mm-gc-red"   => 4.2f,
                        "mm-gc-chain" => 3.2f,
                        _             => 4f
                    };
                    float delay = (dist * 0.28f) % period;

                    <div class="mm-gc @cls"
                        style="animation-delay: -@(delay.ToString("F2"))s;">
                    </div>
                }
            </div>

            @* Background Animated Orbs (2) *@
            @* <div class="mm-orb-1 @(_idleActive ? "orb-active" : "")"></div>
            <div class="mm-orb-2 @(_idleActive ? "orb-active" : "")"></div> *@

            @* Diagonal drifting shapes *@
            @for(int i = 0; i < 14; i++)
            {
                float shapeLeft = (i * 7.3f) % 100f;
                float shapeTop  = (i * 11.7f) % 100f;
                float shapeDur  = 12f + (i * 1.3f) % 10f;
                float shapeDel  = (i * 1.1f) % 8f;
                int   shapeSz   = (i % 3 == 0) ? 18 : (i % 2 == 0) ? 12 : 8;
                float shapeOp   = (i % 3 == 0) ? 0.12f : (i % 2 == 0) ? 0.08f : 0.06f;
                string shapeCol = (i % 5 == 0) ? $"rgba(251,191,36,{shapeOp:F2})"
                                : (i % 3 == 0) ? $"rgba(167,139,250,{shapeOp:F2})"
                                : (i % 2 == 0) ? $"rgba(244,114,182,{shapeOp:F2})"
                                :                 $"rgba(52,211,153,{shapeOp:F2})";
                <div class="mm-shape sq"
                     style="left:@shapeLeft.ToString("F1")%;top:@shapeTop.ToString("F1")%;
                        width:@(shapeSz)px;height:@(shapeSz)px;
                        background-color:@shapeCol;
                        animation-duration:@shapeDur.ToString("F1")s;
                        animation-delay:@shapeDel.ToString("F1")s;">
                </div>
            }

            
            <div class="mm-overlay"></div>

            @* Particles *@
            @for(int i = 0; i < 50; i++)
            {
                float pLeft = (i * 5.3f) % 100f;
                float pDur  = 7f + (i * 0.55f) % 5f;
                float pDel  = (i * 0.85f) % 9f;
                int   pSz   = (i % 4 == 0) ? 4 : 2;
                float pOp   = (i % 2 == 0) ? 0.65f : 0.20f;
                string pBc  = (i % 5 == 0) ? "rgba(251,191,36,"
                            : (i % 3 == 0) ? "rgba(244,114,182,"
                            : "rgba(167,139,250,";
                string pCol = pBc + pOp.ToString("F1") + ")";
                <div class="mm-particle"
                    style="left:@pLeft.ToString("F1")%;width:@(pSz)px;height:@(pSz)px;background-color:@pCol;border-radius:50%;animation-duration:@pDur.ToString("F1")s;animation-delay:@pDel.ToString("F1")s;">
                </div>
            }

        </div>

        
        @* ============================================================================== *@
        @* ============================== MAIN UI LAYOUT ================================ *@
        @* ============================================================================== *@
        <div class="mm-ui">

            @* ------------------------------------------------------------------------- *@
            @* ------------------------- LEFT UI : title + stats ---------------------- *@
            @* ------------------------------------------------------------------------- *@
            <div class="mm-left">
                <div class="mm-eyebrow">FAST PACE GAME</div>
                <div class="mm-title-chain">10</div>
                <div class="mm-title-reaction-wrap">
                    <div class="mm-title-reaction">XPLOSION</div>s
                </div>
              
                <div class="mm-tagline">Place · Detonate · Survive</div>

                @* ------------- Personal stats card (only if save exists) ------------- *@
                @* If a save file exists, display the personal stats section*@
                @if( _saveLoaded && (_save?.TotalRuns ?? 0) > 0 )
                {
                    <div class="mm-stats-personal">
                        
                        @* Best Score *@
                        <div class="mm-sp-row">
                            <span class="mm-sp-icon">🏆</span>
                            <span class="mm-sp-val">@(_save?.BestScore.ToString("N0") ?? "0")</span>
                            <span class="mm-sp-lbl">best score</span>
                        </div>

                        @* Best Wave *@
                        <div class="mm-sp-row">
                            <span class="mm-sp-icon">🌊</span>
                            <span class="mm-sp-val">@(_save?.BestWave ?? 0)</span>
                            <span class="mm-sp-lbl">best wave</span>
                        </div>
                        
                        @* Total Frags *@
                        <div class="mm-sp-row">
                            <span class="mm-sp-icon">💠</span>
                            <span class="mm-sp-val">@(_save?.TotalFragments ?? 0)</span>
                            <span class="mm-sp-lbl">fragments</span>
                        </div>
                        @* Best chain length *@
                        <div class="mm-sp-row">
                            <span class="mm-sp-icon">⛓️</span>
                            <span class="mm-sp-val">@(_save?.BestChainLength ?? 0)</span>
                            <span class="mm-sp-lbl">best chain XPLOSION</span>
                        </div>
                    </div>
                }
                
                @* Hall of Fame (TOP 3 PLAYERS) *@
                @if(_board?.Entries != null && _board.Entries.Length >= 3)
                {
                    <div class="mm-hall-of-fame">
                        <div class="mm-hof-title">🏆 Hall of Fame</div>
                        @for(int i = 0; i < 3 && i < _board.Entries.Length; i++)
                        {
                            var e = _board.Entries[i];
                            string hofMedal = i == 0 ? "🥇" : i == 1 ? "🥈" : "🥉";
                            <div class="mm-hof-row">
                                <span class="mm-hof-medal">@hofMedal</span>
                                <span class="mm-hof-name">@e.DisplayName</span>
                                <span class="mm-hof-score">@((int)e.Value).ToString("N0")</span>
                            </div>
                        }
                    </div>
                }

                @* SIMULATION - TEMP — replace the hall of fame block with this to test layout *@
                @* <div class="mm-hall-of-fame">
                    <div class="mm-hof-title">🏆 Hall of Fame</div>
                    <div class="mm-hof-row">
                        <span class="mm-hof-medal">🥇</span>
                        <span class="mm-hof-name">TestPlayer1</span>
                        <span class="mm-hof-score">1,234,567</span>
                    </div>
                    <div class="mm-hof-row">
                        <span class="mm-hof-medal">🥈</span>
                        <span class="mm-hof-name">TestPlayer2</span>
                        <span class="mm-hof-score">987,654</span>
                    </div>
                    <div class="mm-hof-row">
                        <span class="mm-hof-medal">🥉</span>
                        <span class="mm-hof-name">TestPlayer3</span>
                        <span class="mm-hof-score">456,789</span>
                    </div>
                </div> *@

                @* SIMULATION - TEMP - Replace the force panel block to test layout
                <div class="mm-force-panel">
                    <div class="mm-force-panel-lbl">⚔️ TOP 100 COMBINED FORCE</div>
                    <div class="mm-force-bar-track">
                        <div class="mm-force-bar-fill" style="width:67%;"></div>
                    </div>
                    <div class="mm-force-panel-sub">67.0% toward next milestone</div>
                </div> *@

                @* TOP 100 CONTRIBUTOR PROGRESS BAR *@
                @if(_forceBarLoaded)
                {
                    <div class="mm-force-panel">
                        <div class="mm-force-panel-lbl">⚔️ TOP 100 COMBINED FORCE</div>
                        <div class="mm-force-bar-track">
                            <div class="mm-force-bar-fill" style="width:@GetForceBarPct().ToString("F1")%;"></div>
                        </div>
                        <div class="mm-force-panel-sub">@GetForceBarPct().ToString("F1")% toward next milestone</div>
                    </div>
                }

                <div class="mm-ver">10XPLOSION · Early Access</div>
                  
            </div>

            @* ------------------------------------------------------------------------- *@
            @* ----------------- MIDDLE UI : tab panels rendered here ------------------ *@
            @* ------------------------------------------------------------------------- *@
            <div class="mm-center">
            
            @* Render Panels for tabs clicked buttons 'Pets, Leaderboard, Shop...'*@

                @* ============================================================ *@
                @* ================ TAB : COMMUNITY =========================== *@
                @* ============================================================ *@
            
                @if(_tab == "community")
                {
                    <div class="mm-panel">
                        <div class="mm-content-card mm-center-enter">

                            <div class="mm-comm-from">FROM ME (the dev)</div>

                            <div class="mm-comm-greeting">Hey! :) 👋</div>
                            <div class="mm-comm-body">You're playing during Early Access — that means you're one of the first, and your feedback genuinely shapes what gets built next. Thanks for that!</div>

                            @if(!string.IsNullOrEmpty(_community?.Devnote))
                            {
                                <div class="mm-comm-devnote">
                                    <span class="mm-comm-devnote-label">THIS WEEK</span>
                                    <span class="mm-comm-devnote-text">@_community.Devnote</span>
                                </div>
                            }

                            @*<div class="mm-letter-divider"></div>*@

                            <div class="mm-letter-section-title">📅 What's next</div>
                            @foreach(var ev in (_community?.Events ?? new List<CommunityEvent>()))
                            {
                                if(string.IsNullOrEmpty(ev.Text)) continue;

                                string badgeClass = ev.Color switch {
                                    "green"  => "mm-badge-green",
                                    "purple" => "mm-badge-purple",
                                    "yellow" => "mm-badge-yellow",
                                    _        => "mm-badge-gray"
                                };
                                <div class="mm-community-event-row">
                                    <span class="mm-event-badge @badgeClass">@ev.Badge</span>
                                    <span class="mm-event-text">@ev.Text</span>
                                </div>
                            }


                            <div class="mm-comm-discord-row">
                                <div class="mm-comm-discord-text">Got ideas, bugs, or just want to share a wild chain? Come say hi — it's just me on the other side.</div>
                                <button class="mm-discord-btn @(_discordCopied ? "copied" : "")" 
                                        onclick=@OnDiscordCopy 
                                        style="pointer-events:all;">
                                    @(_discordCopied ? "✓ Link Copied!" : "💬 Join Discord")
                                </button>
                            </div>

                        </div>
                    </div>
                }
                @* ============================================================ *@
                @* ============== TAB : LEADERBOARD =========================== *@
                @* ============================================================ *@
                @if(_tab == "lb")
                {
                    <div class="mm-panel">
                        <div class="mm-content-card mm-center-enter">
                            <div class="mm-panel-hd">🏅 Leaderboard</div>

                            @* Mode toggle *@
                            <div class="mm-lb-mode-tabs">
                                <button class="mm-lb-mode-btn @(_lbMode == "score" ? "on" : "")"
                                        onclick=@(() => { _lbMode = "score"; _ = LoadLb(); StateHasChanged(); })>
                                    🏆 Score
                                </button>
                                <button class="mm-lb-mode-btn @(_lbMode == "pet" ? "on" : "")"
                                        onclick=@(() => { _lbMode = "pet"; _ = LoadPetLb(); StateHasChanged(); })>
                                    🐾 Pet
                                </button>
                                  <button class="mm-lb-mode-btn @(_lbMode == "chain" ? "on" : "")"
                                        onclick=@(() => { _lbMode = "chain"; _ = LoadChainLb(); StateHasChanged(); })>
                                    ⛓ Chain
                                </button>
                                <button class="mm-lb-mode-btn @(_lbMode == "force" ? "on" : "")"
                                        onclick=@(() => { _lbMode = "force"; _ = LoadForceLb(); StateHasChanged(); })>
                                    ⚔️ Force
                                </button>
                            </div>

                            @* Leaderboard - SCORE *@
                            @if(_lbMode == "score")
                            {
                                @if(_loadingLb)
                                {
                                    <div class="mm-panel-empty">Loading...</div>
                                }
                                else if(_board?.Entries == null || _board.Entries.Length == 0)
                                {
                                    <div class="mm-panel-empty">No entries yet — be the first!</div>
                                }
                                else
                                {
                                    @for(int i = 0; i < _board.Entries.Length; i++)
                                    {
                                        var e2 = _board.Entries[i];
                                        int rk = i + 1;
                                        string medal = rk==1?"🥇":rk==2?"🥈":rk==3?"🥉":$"#{rk}";
                                        <div class="mm-lb-row @(e2.Me?"me":"")">
                                            <span class="mm-lb-rank">@medal</span>
                                            <span class="mm-lb-name">@e2.DisplayName</span>
                                            <span class="mm-lb-score">@((int)e2.Value).ToString("N0")</span>
                                        </div>
                                    }
                                }
                            }
                            @* Leaderboard - Pet XP & Level *@
                            else if (_lbMode == "pet")
                            {
                                @if(_loadingLb)
                                {
                                    <div class="mm-panel-empty">Loading...</div>
                                }
                                else if(_petBoard?.Entries == null || _petBoard.Entries.Length == 0)
                                {
                                    <div class="mm-panel-empty">No pet data yet!</div>
                                }
                                else
                                {
                                    @for(int i = 0; i < _petBoard.Entries.Length; i++)
                                    {
                                        var e2 = _petBoard.Entries[i];
                                        int rk = i + 1;
                                        string medal = rk==1?"🥇":rk==2?"🥈":rk==3?"🥉":$"#{rk}";
                                        int encoded = (int)e2.Value;
                                        int lvl = encoded / 1_000_000;
                                        int xp  = encoded % 1_000_000;
                                        string petEmoji = lvl switch {
                                            1 => "ui/icons/egg/egg_1.png", 2 => "🐣", 3 => "🐥", 4 => "🦅", 5 => "🐉", _ => "👑"
                                        };
                                        <div class="mm-lb-row @(e2.Me?"me":"")">
                                            <span class="mm-lb-rank">@medal</span>
                                            <span class="mm-lb-name">@e2.DisplayName</span>
                                            <span class="mm-lb-pet-badge">@petEmoji Lv@lvl</span>
                                            <span class="mm-lb-score">@xp xp</span>
                                        </div>
                                    }
                                }
                            }
                            @* Leaderboard - Best Chain Reaction Sequence (+score - players)*@
                            else if(_lbMode == "chain")
                            {
                                @if(_loadingLb)
                                {
                                    <div class="mm-panel-empty">Loading...</div>
                                }
                                else if(_chainBoard?.Entries == null || _chainBoard.Entries.Length == 0)
                                {
                                    <div class="mm-panel-empty">No chain data yet!</div>
                                }
                                else
                                {
                                    @for(int i = 0; i < _chainBoard.Entries.Length; i++)
                                    {
                                        var entry = _chainBoard.Entries[i];
                                        int rk = i + 1;
                                        string medal = rk==1?"🥇":rk==2?"🥈":rk==3?"🥉":$"#{rk}";
                                        int encoded  = (int)entry.Value;
                                        int chainLen = encoded / 10_000_000;
                                        int tieScore = encoded % 10_000_000;
                                        <div class="mm-lb-row @(entry.Me?"me":"")">
                                            <span class="mm-lb-rank">@medal</span>
                                            <div class="mm-lb-name-col">
                                                <span class="mm-lb-name">@entry.DisplayName</span>
                                                <span class="mm-lb-chain-title">@GetChainTitle(chainLen)</span>
                                            </div>
                                            <span class="mm-lb-chain-badge">⛓ @chainLen</span>
                                            <span class="mm-lb-score">@tieScore.ToString("N0")</span>
                                        </div>
                                    }
                                }
                            }

                            @* TOP 100 GLOBAL XP BAR CONTRIBUTORS *@
                            else if(_lbMode == "force")
                            {
                                @if(_loadingLb)
                                {
                                    <div class="mm-panel-empty">Loading...</div>
                                }
                                else if(_forceBoard?.Entries == null || _forceBoard.Entries.Length == 0)
                                {
                                    <div class="mm-panel-empty">No force data yet!</div>
                                }
                                else
                                {
                                    @* Progress summary at top *@
                                    <div class="mm-force-summary">
                                        <span class="mm-force-summary-lbl">⚔️ Combined Force</span>
                                        <div class="mm-force-bar-track">
                                            <div class="mm-force-bar-fill" style="width:@GetForceBarPct().ToString("F1")%;"></div>
                                        </div>
                                        <span class="mm-force-summary-pct">@GetForceBarPct().ToString("F1")%</span>
                                    </div>

                                    @for(int i = 0; i < _forceBoard.Entries.Length; i++)
                                    {
                                        var entry  = _forceBoard.Entries[i];
                                        int rk     = i + 1;
                                        string medal = rk==1?"🥇":rk==2?"🥈":rk==3?"🥉":$"#{rk}";

                                        // Decode medal from score if encoded
                                        long score    = (long)entry.Value;
                                        string myMedal = entry.Me ? GetMedalEmoji(_save?.ForceMedalTier ?? 0) : "";
                                        float pct = _forceTotalScore > 0 ? (float)score / _forceTotalScore * 100f : 0f;

                                        <div class="mm-lb-row @(entry.Me?"me":"")">
                                            <span class="mm-lb-rank">@medal</span>
                                            <div class="mm-lb-name-col">
                                                <span class="mm-lb-name">
                                                   @(myMedal != "" ? myMedal + " " : "")@entry.DisplayName
                                                </span>
                                            </div>
                                            <div class="mm-force-bar-mini-wrap">
                                                <div class="mm-force-bar-mini">
                                                    <div class="mm-force-bar-mini-fill" style="width:@pct.ToString("F1")%;"></div>
                                                </div>
                                                <span class="mm-force-pct">@pct.ToString("F1")%</span>
                                            </div>
                                            <span class="mm-lb-score">@score.ToString("N0")</span>
                                        </div>
                                    }
                                }
                            }
                        </div>
                    </div>
                }

                @* ============================================================ *@
                @* ============== TAB : STATS ================================= *@
                @* ============================================================ *@
                else if( _tab == "stats" )
                {
                    <div class="mm-panel">
                        <div class="mm-content-card mm-center-enter"> @*Card bg surrounding div*@
                            <div class="mm-panel-hd">📊 Lifetime Stats</div>
                            <div class="mm-srow"><span class="mm-slbl">Total Runs</span>     <span class="mm-sval">@(_save?.TotalRuns??0)</span></div>
                            <div class="mm-srow"><span class="mm-slbl">Chests Cleared</span> <span class="mm-sval">@(_save?.TotalChestsCleared??0)</span></div>
                            <div class="mm-srow"><span class="mm-slbl">Challenges Done</span><span class="mm-sval">@(_save?.TotalChallengesCompleted??0)</span></div>
                            <div class="mm-srow"><span class="mm-slbl">Time Played</span>    <span class="mm-sval">@GetTimePlayed()</span></div>
                            <div class="mm-srow"><span class="mm-slbl">Best Score</span>     <span class="mm-sval">@(_save?.BestScore.ToString("N0")??"0")</span></div>
                            <div class="mm-srow"><span class="mm-slbl">Best Wave</span>      <span class="mm-sval">@(_save?.BestWave??0)</span></div>
                        </div>
                    </div>
                }

                @* ============================================================ *@
                @* ============== TAB : SHOP ================================== *@
                @* ============================================================ *@
                else if( _tab == "shop" )
                {
                    <div class="mm-panel">
                        <div class="mm-content-card mm-center-enter"> @*Card bg surrounding div*@
                            <div class="mm-panel-hd">💠 Fragment Shop</div>
                            
                            @* --------- Shop section tabs --------- *@
                            @* Lets the player switch between different shop categories (modifiers / consumables / pets / bombs)*@
                            <div class="mm-shop-tabs">

                                @* ── Modifiers tab button ── *@
                                <button class="mm-shop-tab @(_shopTab == "mods" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")" 
                                        onclick=@(() => { 
                                            if(MainMenuTutorial.Instance?.IsActive != true) 
                                            { _shopTab = "mods"; StateHasChanged(); Sound.Play("UI_Button"); } })>
                                    🎲 Modifiers
                                </button>

                                @* ── Consumables tab button ── *@
                                <button class="mm-shop-tab @(_shopTab == "consumables" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")"
                                        onclick=@(() => { if(MainMenuTutorial.Instance?.IsActive != true) 
                                        { _shopTab = "consumables"; StateHasChanged(); Sound.Play("UI_Button"); } })>
                                    🎁 Consumables
                                </button>

                                <button class="mm-shop-tab @(_shopTab == "pets" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")"
                                        onclick=@(() => { if(MainMenuTutorial.Instance?.IsActive != true) 
                                        { _shopTab = "pets"; StateHasChanged(); 
                                        Sound.Play("UI_Button"); Sound.Play("Meow_cat");} })>
                                    @(_save?.PetUnlockShown == true ? "🐾 Pets" : "📦 ???")
                                </button>

                                @* ── Bombs tab button ── *@
                                <button class="mm-shop-tab @(_shopTab == "bombs" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")"
                                        onclick=@(() => { 
                                            if(MainMenuTutorial.Instance?.IsActive != true) { 
                                                _shopTab = "bombs"; StateHasChanged(); Sound.Play("UI_Button"); } })>
                                    💣 Bombs
                               </button>
                            </div>
                            
                            @* ---------------------------------------------------- *@
                            @* ----- Shop sub-section : MODIFIERS ----------------- *@
                            @* ---------------------------------------------------- *@
                            @if(_shopTab == "mods")
                            {
                                @* --------- Shop balance --------- *@
                                @* Displays our amount of fragments (update in real time)*@
                                <div class="mm-shop-balance">
                                    <span class="mm-shop-balance-icon">💠</span>
                                    <span class="mm-shop-balance-val">@(_save?.TotalFragments ?? 0)</span>
                                    <span class="mm-shop-balance-lbl">fragments</span>
                                </div>

                                @* Let player know how much modifier he still can equip *@
                                <div class="mm-shop-slots">
                                    <span class="mm-shop-slots-lbl">Active modifiers</span>
                                    <span class="mm-shop-slots-val">
                                        @(_save?.ActiveModifiers?.Count ?? 0) / @PlayerSave.MaxActiveModifiers
                                    </span>
                                </div>

                                @* --------- Shop grid --------- *@
                                @* Displays the grid of modifiers elements that can be bought with frags*@
                                <div class="mm-shop-grid">
                                    @* Loop through all our modifiers available in catalog to filter them*@
                                    @foreach(var mod in ShopCatalog.All)
                                    {
                                        // do we already own it ?
                                        bool owned = _save?.OwnsModifier(mod.Id) ?? false;
                                        // do we have it active ?
                                        bool active = _save?.ModifierActive(mod.Id) ?? false;
                                        // can we afford it ? (if not bought yet)
                                        bool canAfford = (_save?.TotalFragments ?? 0) >= mod.Cost;

                                        // retrieves the rarity for this modifier
                                        string rarityClass = mod.Rarity switch
                                        {
                                            ModifierRarity.Rare => "rarity-rare",
                                            ModifierRarity.Epic => "rarity-epic",
                                            _ => "rarity-common"
                                        };

                                        // ----- A single modifier card in the shop grid ----- //
                                        <div class="mm-mod-card @rarityClass @(owned ? "owned" : "") @(active ? "active" : "") 
                                        @(!active && owned && (_save?.ActiveModifiers?.Count ?? 0) >= PlayerSave.MaxActiveModifiers ? "capped" : "") 
                                        @(!owned && (_save?.ActiveModifiers?.Count ?? 0) >= PlayerSave.MaxActiveModifiers ? "capped-unowned" : "")">
                                            @* these above, show the different states of the cards
                                            i.e : The 4th tells us if we still can equip a modifier (otherwise it's capped and card turns red)
                                            i.e : The 5th tells us that, if we don't have this modifier, and we still can equip, it returns a diff color 'capped-unowned'*@

                                            @* Add icons ; name ; desc ; footer - on that card*@
                                            <div class="mm-mod-icon">@mod.Icon</div>
                                            <div class="mm-mod-name">@mod.Name</div>
                                            <div class="mm-mod-desc">@mod.Description</div>

                                               @* ── Pushes button to bottom ── *@
                                            <div class="mm-mod-footer">
                                                <div>
                                                    @* Is this modifier owned or not*@

                                                    @* Yes : Show Active Button (or unlocked if bomb slot) *@
                                                    @if(owned)
                                                    {
                                                        // Bomb slots are auto-applied — show "Unlocked" instead of toggleable
                                                        bool isBombSlot = mod.Id == ModifierId.BombSlot2 
                                                                    || mod.Id == ModifierId.BombSlot3 
                                                                    || mod.Id == ModifierId.BombSlot4;
                                                    @if(isBombSlot)
                                                        {
                                                            <button class="mm-mod-btn btn-active" disabled="disabled">
                                                                <span>@(mod.Id == ModifierId.BombSlot2 ? "🎁 Gift" : "✓ Unlocked")</span>
                                                            </button>
                                                        }
                                                        else
                                                        {
                                                            // Do we already have 3 modifiers equipped ? (max)
                                                            bool capReached = !active && (_save?.ActiveModifiers?.Count ?? 0) >= PlayerSave.MaxActiveModifiers;

                                                            // Button for toggling modifiers that we own already (active/inactive/capped (if already 3 active 🟢))
                                                            <button class="mm-mod-btn @(active ? "btn-active" : capReached ? "btn-capped" : "btn-inactive")"
                                                                    onclick=@(() => OnToggleMod(mod.Id))>
                                                                @(active ? "🟢 Active" : capReached ? "🔴 Slots Full" : "⚔️ Equip") @*🔌*@
                                                            </button>
                                                        }
                                                    }
                                                    else // No : Show Buy Button
                                                    {
                                                        <button class="mm-mod-btn btn-buy @(canAfford? "" : "cant-afford")"
                                                                onclick="@(() => OnBuyMod(mod.Id, mod.Cost))">
                                                            💠 @mod.Cost
                                                        </button>
                                                    }
                                                </div>
                                            </div>
                                        </div>
                                    }
                                </div>
                            }

                            
                            @* ---------------------------------------------------- *@
                            @* ----- Shop sub-section : CONSUMABLES -------------- *@
                            @* ---------------------------------------------------- *@
                            @if (_shopTab == "consumables")
                            {
                                @* ── Balance ── *@
                                <div class="mm-shop-balance">
                                    <span class="mm-shop-balance-icon">💠</span>
                                    <span class="mm-shop-balance-val">@(_save?.TotalFragments ?? 0)</span>
                                    <span class="mm-shop-balance-lbl">fragments</span>
                                </div>

                                @* ── Carry cap info ── *@
                                <div class="mm-shop-slots">
                                    <span class="mm-shop-slots-lbl">Max 3 consumables per run (any mix)</span>
                                    <span class="mm-shop-slots-val">@($"{_save?.TotalConsumablesOwned ?? 0} / 3")</span>
                                </div>

                                @* ── Consumables grid ── *@
                                <div class="mm-shop-grid">
                                    @foreach (var cons in ConsumableCatalog.All)
                                    {
                                        int owned       = _save?.GetConsumableCount(cons.Id) ?? 0;
                                        bool canAfford  = (_save?.TotalFragments ?? 0) >= cons.Cost;
                                        bool atCap      = (_save?.TotalConsumablesOwned ?? 0) >= 3;

                                        // lock every button except Heart consumable buy btn (menu tuto)
                                        bool tutLocked = MainMenuTutorial.Instance?.IsActive == true 
                                        && MainMenuTutorial.Instance?.Step == 4
                                        && cons.Id != ConsumableId.Heart;

                                        // Heart-specific cap based on pet level
                                        bool atHeartCap = cons.Id == ConsumableId.Heart &&
                                                        owned >= (_save?.MaxHeartConsumables ?? 2);

                                        <div class="mm-mod-card mm-consumable-card">
                                            <div class="mm-mod-icon">@cons.Icon</div>
                                            <div class="mm-mod-name">@cons.Name</div>
                                            <div class="mm-mod-desc">@cons.Description</div>

                                            <div class="mm-bomb-footer">
                                                @* Stock badge — how many you currently own *@
                                                @if (owned > 0)
                                                {
                                                    <div class="mm-consumable-stock">
                                                        @($"In stock: {owned}")
                                                    </div>
                                                }

                                                @* Buy button — disabled if at cap or can't afford *@
                                               <button class="mm-mod-btn btn-buy @(canAfford && !atCap && !atHeartCap && !tutLocked ? "" : "cant-afford")"
                                                onclick=@(() => { if(!tutLocked) OnBuyConsumable(cons.Id, cons.Cost); })
                                                disabled="@(atCap || !canAfford || atHeartCap || tutLocked)">

                                                    @* if this consumable card is a heart consu, and cap is reached*@
                                                    @if(atHeartCap)
                                                    {
                                                        @("❤️ Limit Reached")
                                                    }
                                                    @* if the cap for all consumables is reached *@
                                                    else if (atCap && owned == 0)
                                                    {
                                                        @("🔴 Slots Full")
                                                    }
                                                    // otherwise, show cost if can buy
                                                    else
                                                    {
                                                        @($"💠 {cons.Cost}")
                                                    }
                                                </button>
                                            </div>
                                        </div>
                                    }
                                </div>
                            }

                            @* ----- Future shop sub-sections will go here -------- *@
                            @*              Consumables / Pets / Bombs              *@
                            @* ---------------------------------------------------- *@

                            @if (_shopTab == "pets")
                            {
                                <div class="mm-shop-balance">
                                    <span class="mm-shop-balance-icon">💠</span>
                                    <span class="mm-shop-balance-val">@(_save?.TotalFragments ?? 0)</span>
                                    <span class="mm-shop-balance-lbl">fragments</span>
                                </div>

                                @if(_save?.PetUnlockShown != true)
                                {
                                    @* ── Locked — pet not yet revealed ── *@
                                    <div class="mm-pet-display">
                                        <div class="mm-pet-emoji">📦</div>
                                        <div class="mm-pet-name">???</div>
                                        <div class="mm-pet-level">Unlock after 3 runs</div>
                                    </div>
                                    <div class="mm-shop-slots">
                                        <span class="mm-shop-slots-lbl">🎨 Cosmetic skins — coming soon</span>
                                    </div>
                                }
                                else
                                {
                                    @* ── Unlocked — show full pet ── *@
                                    <div class="mm-pet-display">
                                        <div class="mm-pet-emoji">
                                            @if(GetPetImagePath() != null)
                                            {
                                                <image src=@GetPetImagePath() class="@((_save?.PetLevel ?? 1) == 2 ? "cat-sprite-anim" : "")"/>
                                            }
                                            else
                                            {
                                                @GetPetEmoji()
                                            }
                                        </div>
                                        <div class="mm-pet-name">@GetPetName()</div>
                                        <div class="mm-pet-level">Level @(_save?.PetLevel ?? 1)</div>
                                        <div class="mm-pet-xp-track">
                                            <div class="mm-pet-xp-fill" style="width:@GetPetXpPct()%;"></div>
                                        </div>
                                        <div class="mm-pet-xp-label">
                                            @($"{_save?.PetXp ?? 0} / {GetPetXpNeeded()} XP to next level")
                                        </div>
                                    </div>
                                    @if ((_save?.PetLevel ?? 1) < 6)
                                    {
                                        <div class="mm-pet-next">
                                            <span class="mm-pet-next-label">Next evolution</span>
                                            <span class="mm-pet-next-emoji">@GetNextPetEmoji()</span>
                                            <span class="mm-pet-next-hint">Keep playing to evolve!</span>
                                        </div>
                                    }
                                    else
                                    {
                                        <div class="mm-pet-next">
                                            <span class="mm-pet-next-label">🌟 Maximum level reached!</span>
                                        </div>
                                    }
                                    <div class="mm-shop-slots">
                                        <span class="mm-shop-slots-lbl">🎨 Cosmetic skins — coming soon</span>
                                    </div>
                                }
                            }
                            @* ---------------------------------------------------- *@
                            @* ------------ Shop sub-section : BOMBS -------------- *@
                            @* ---------------------------------------------------- *@

                            @if (_shopTab == "bombs")
                            {
                                @* ── Balance display ── *@
                                <div class="mm-shop-balance">
                                    <span class="mm-shop-balance-icon">💠</span>
                                    <span class="mm-shop-balance-val">@(_save?.TotalFragments ?? 0)</span>
                                    <span class="mm-shop-balance-lbl">fragments</span>
                                </div>

                                @* ── Info line ── *@
                                <div class="mm-shop-slots">
                                    <span class="mm-shop-slots-lbl">Owned bombs appear in your hand each wave</span>
                                </div>

                                @* ── Bombs grid ── *@
                                <div class="mm-shop-grid">
                                    @foreach (var bomb in BombCatalog.All)
                                    {
                                        // Only show trial badge for bombs that are part of the trial
                                        bool isTrialBomb = bomb.Type == GridManager.BombType.Sniper;
                                                    
                                        bool owned      = _save?.OwnsBomb(bomb.Type) ?? false;
                                        bool canAfford  = (_save?.TotalFragments ?? 0) >= bomb.Cost;
                                        bool isTrial = (_save?.IsInTrial == true) && isTrialBomb && !owned;

                                        @* ── Single bomb card ── *@
                                        <div class="mm-mod-card mm-bomb-card @(owned ? "owned" : "")">

                                            @* Icon *@
                                            <div class="mm-mod-icon">@bomb.Icon</div>

                                            @* Name *@
                                            <div class="mm-mod-name">@bomb.Name</div>

                                            @* Blast pattern hint *@
                                            <div class="mm-bomb-hint">@bomb.Hint</div>

                                            @* Description *@
                                            <div class="mm-mod-desc">@bomb.Description</div>

                                            @* Trial badge — shown if player still has trial runs left and doesn't own it *@

                                            @* ── Pushes badge + button to bottom of card ── *@
                                            <div class="mm-bomb-footer">
                                                @if (isTrial)
                                                {
                                                    <div class="mm-bomb-trial-badge">
                                                        @($"🎁 Trial: {_save?.TrialBombsRunsLeft ?? 0} runs left")
                                                    </div>
                                                }

                                                @* Buy / Owned button *@
                                                <div>
                                                    @if (owned)
                                                    {
                                                        bool isStarterBomb = bomb.Type == GridManager.BombType.Diagonal;

                                                        <button class="mm-mod-btn btn-active" disabled="disabled">
                                                            @(isStarterBomb ? "🎁 Gift" : "✓ Unlocked")
                                                        </button>
                                                    }
                                                    else
                                                    {
                                                        <button class="mm-mod-btn btn-buy @(canAfford ? "" : "cant-afford")"
                                                                onclick=@(() => OnBuyBomb(bomb.Type, bomb.Cost))>
                                                            💠 @bomb.Cost
                                                        </button>
                                                    }
                                                </div>
                                            </div>
                                        </div>
                                    }
                                </div>
                            }
                        </div>
                    </div>
                }

        @* ============================================================ *@
        @* ============== TAB : PET FARM (idle grid) ================== *@
        @* ============================================================ *@
        else if( _tab == "farm" )
        {
            <div class="mm-panel">
                <div class="mm-content-card mm-center-enter">

                    @* ── LOCKED STATE — shown before 3 runs completed ── *@
                    @if(_save?.PetUnlockShown != true)
                    {
                        <div class="mm-pet-locked-card">
                            @* Big mystery chest *@
                            <div class="mm-pet-locked-chest">📦</div>

                            @* Title *@
                            <div class="mm-pet-locked-title">???</div>

                            @* How many runs left *@
                        <div class="mm-pet-locked-desc">
                            Complete <span class="mm-pet-locked-count">@(MathF.Max(0, 3 - (_save?.TotalRuns ?? 0)).ToString("F0"))</span> 
                            <span> more run@((3 - (_save?.TotalRuns ?? 0)) == 1 ? "" : "s") to reveal</span>
                        </div>

                            @* Progress bar + pips *@
                            <div class="mm-pet-locked-progress">
                                <div class="mm-pet-locked-bar">
                                    <div class="mm-pet-locked-fill"
                                        style="width:@(MathF.Min(100f, (_save?.TotalRuns ?? 0) / 3f * 100f).ToString("F0"))%;">
                                    </div>
                                </div>
                                <div class="mm-pet-locked-pips">
                                    @for(int i = 0; i < 3; i++)
                                    {
                                        bool done = (_save?.TotalRuns ?? 0) > i;
                                        <div class="mm-pet-locked-pip @(done ? "done" : "")">
                                            @(done ? "✓" : (i + 1).ToString())
                                        </div>
                                    }
                                </div>
                            </div>
                        </div>
                    }

                    @* ── UNLOCKED STATE — shown after pet is revealed ── *@
                    else
                    {
                        @* Header *@
                        <div class="mm-panel-hd">🐾 Your Pet</div>

                        @* ── Pet card : emoji + name + rename button ── *@
                        <div class="mm-pet-card">

                            @* Big pet emoji *@
                           <div class="mm-pet-hero-emoji">
                            @if(GetPetImagePath() != null)
                            {
                                <Image src=@GetPetImagePath()/>
                            }
                            else
                            {
                                @GetPetEmoji()
                            }
                        </div>

                            @* Name editing mode *@
                            @if(_editingPetName)
                            {
                                <div class="mm-pet-name-edit">
                                    <div class="mm-pet-name-prompt">Name your companion</div>
                                    <div @ref="_nameEntryContainer" class="mm-pet-name-input-wrap"></div>
                                    <div class="mm-pet-name-hint">Type · click ✓ to confirm</div>
                                    <div class="mm-pet-name-buttons">
                                        <button class="mm-pet-name-confirm" style="pointer-events:all;" 
                                                onclick=@OnConfirmPetName>✓ Confirm</button>
                                        <button class="mm-pet-name-cancel" style="pointer-events:all;" 
                                                onclick=@(() => { _editingPetName = false; CloseNameEntry(); StateHasChanged(); })>✕</button>
                                    </div>
                                </div>
                            }
                            @* Name display mode *@
                            else
                            {
                                <div class="mm-pet-name-row">
                                    <div class="mm-pet-hero-name">@GetDisplayPetName()</div>
                                    <button class="mm-pet-rename-btn" onclick=@(() => {
                                            _editingPetName = true;
                                            _petNameInput = GetDisplayPetName();
                                            StateHasChanged();
                                            OpenNameEntry();
                                        })>✏️</button>
                                </div>
                            }
                        </div>

                        @* ── Level label ── *@
                        <div class="mm-pet-hero-level">Level @(_save?.PetLevel ?? 1)</div>

                        @* ── XP progress bar ── *@
                        <div class="mm-pet-xp-track">
                            <div class="mm-pet-xp-fill" style="width:@GetPetXpPct()%;"></div>
                        </div>

                        @* ── XP label ── *@
                        <div class="mm-pet-xp-label">
                            @($"{_save?.PetXp ?? 0} / {GetPetXpNeeded()} XP to evolve!")
                        </div>

                        @* ── Stat bonuses — one row per level ── *@
                        <div class="mm-pet-stats">

                            @* Lv 1 — always active *@
                            <div class="mm-pet-stat-row">
                                <span class="mm-pet-stat-icon">💠</span>
                                <span class="mm-pet-stat-lbl">Fragments per 20 waves</span>
                                <span class="mm-pet-stat-val">+5 frags</span>
                            </div>

                            @* Lv 2 *@
                            <div class="mm-pet-stat-row">
                                <span class="mm-pet-stat-icon">❤️</span>
                                <span class="mm-pet-stat-lbl">Bonus starting life</span>
                                @if((_save?.PetLevel ?? 1) >= 2)
                                { <span class="mm-pet-stat-val">+1 life</span> }
                                else
                                { <span class="mm-pet-stat-val locked">Unlocks at Lv 2</span> }
                            </div>

                            @* Lv 3 *@
                            <div class="mm-pet-stat-row">
                                <span class="mm-pet-stat-icon">💥</span>
                                <span class="mm-pet-stat-lbl">Multi-hit chests pre-damaged</span>
                                @if((_save?.PetLevel ?? 1) >= 3)
                                { <span class="mm-pet-stat-val">-1 hit on spawn</span> }
                                else
                                { <span class="mm-pet-stat-val locked">Unlocks at Lv 3</span> }
                            </div>

                            @* Lv 4 *@
                            <div class="mm-pet-stat-row">
                                <span class="mm-pet-stat-icon">🌊</span>
                                <span class="mm-pet-stat-lbl">Double frags on wave 10, 20...</span>
                                @if((_save?.PetLevel ?? 1) >= 4)
                                { <span class="mm-pet-stat-val">x2 frags</span> }
                                else
                                { <span class="mm-pet-stat-val locked">Unlocks at Lv 4</span> }
                            </div>

                            @* Lv 5 *@
                            <div class="mm-pet-stat-row">
                                <span class="mm-pet-stat-icon">🐉</span>
                                <span class="mm-pet-stat-lbl">5+ chest chain score bonus</span>
                                @if((_save?.PetLevel ?? 1) >= 5)
                                { <span class="mm-pet-stat-val">+15% score</span> }
                                else
                                { <span class="mm-pet-stat-val locked">Unlocks at Lv 5</span> }
                            </div>

                            @* Lv 6 *@
                            <div class="mm-pet-stat-row">
                                <span class="mm-pet-stat-icon">👑</span>
                                <span class="mm-pet-stat-lbl">Second bonus life</span>
                                @if((_save?.PetLevel ?? 1) >= 6)
                                { <span class="mm-pet-stat-val">+1 more life (3 total)</span> }
                                else
                                { <span class="mm-pet-stat-val locked">Unlocks at Lv 6</span> }
                            </div>

                        </div>

                        @* ── Next evolution teaser ── *@
                        @if((_save?.PetLevel ?? 1) < 6)
                        {
                            <div class="mm-pet-next-hint">
                                Next: @GetNextPetEmoji() @GetNextPetName() — @(GetPetXpNeeded() - (_save?.PetXp ?? 0)) runs away
                            </div>
                        }
                        else
                        {
                            <div class="mm-pet-next-hint">🌟 Maximum level — fully evolved</div>
                        }
                    }

                </div>
            </div>
        }
        @* ============================================================ *@
        @* =================== TAB : CREDITS PAGE ===================== *@
        @* ============================================================ *@

        else if( _tab == "credits" )
        {
            <div class="mm-panel">
                <div class="mm-content-card mm-center-enter">
                    <div class="mm-panel-hd">🎖️ Credits</div>

                    <div class="mm-credits-section">
                        <div class="mm-credits-role">🎮 Game Design, Code & Art :</div>
                        <div class="mm-credits-name">Purevsion</div>
                    </div>

                    <div class="mm-credits-section">
                        <div class="mm-credits-role">🔊 Sound Effects :</div>
                        <div class="mm-credits-name">Pixabay — pixabay.com /</div>
                        <div class="mm-credits-name">Freesound — freesound.org</div>
                    </div>

                    @* <div class="mm-credits-section">
                        <div class="mm-credits-role">🎵 Music</div>
                        <div class="mm-credits-name">Matt Deconick</div>
                        <button class="mm-musiclink-btn  @(_musicLinkCopied ? "copied" : "")"
                                onclick=@OnMusicLinkCopy
                                style="pointer-events:all; margin-top:-8px;">
                            @(_musicLinkCopied ? "✓ Link Copied!" : "🎵 Copy Profile Link")
                        </button>
                    </div> *@

                    <div class="mm-credits-section">
                        <div class="mm-credits-role">💙 Special Thanks :</div>
                        <div class="mm-credits-name">Everyone in the S&Box community playing during Early Access 💙</div>
                        @* <div class="mm-credits-name"></div> *@
                    </div>

                    <div class="mm-credits-footer">
                        Made solo with 💥 and too much caffein..
                    </div>
                </div>
            </div>
        }
        @* ── End of mm-center panels ── *@
        </div>
            @* ------------------------------------------------------------------------- *@
            @* ------------------ RIGHT UI : play button + tabs ----------------------- *@
            @* ------------------------------------------------------------------------- *@
            <div class="mm-right">

                 @* Dev menu — visible only in dev build *@
                @if(DEV_MODE)
                {
                    <div class="mm-dev-menu">
                        <button class="mm-dev-btn" onclick=@OnWipeSave>🗑️ Wipe Save</button>
                        <button class="mm-dev-btn" onclick=@OnAddFrags>💠 +1000 Frags</button>
                        <button class="mm-dev-btn" onclick=@OnUnlockAll>🔓 Unlock All</button>
                        <button class="mm-dev-btn" onclick=@(() => { 
                            _evoFromLevel = _save?.PetLevel ?? 1; 
                            _save.PetLevel++;  // simulate level up
                            TriggerEvolution(); 
                            StateHasChanged(); 
                        })>🐣 Test Evo</button>
                    </div>
                }

                @* Big Play Button ▶ *@ @* Prevents clicking on it if still on MainMenu Tutorial *@
                <div class="mm-play-wrap">
                    <button class="mm-play @(MainMenuTutorial.Instance?.IsActive == true ? "mm-play-locked" : "")" 
                            onclick=@(() => { if(MainMenuTutorial.Instance?.IsActive != true) OnPlay();
                            Sound.Play("PlayButton"); })>
                        <div class="mm-play-shine"></div>
                        <span>▶</span>
                        <span class="mm-play-label">PLAY</span>
                    </button>
                </div>

            @* =================== SURVIVAL MODE  =================== *@
            @{
                bool survivalUnlocked = _save?.TutorialCompleted ?? false;
            }
            <div class="mm-multiplayer-wrap">
                <button class="mm-multiplayer @(survivalUnlocked && MainMenuTutorial.Instance?.IsActive != true ? "" : "mm-mode-locked")"
                        onclick=@(() => { 
                            if(survivalUnlocked && MainMenuTutorial.Instance?.IsActive != true) 
                            { OnPlaySurvival(); Sound.Play("PlayButton"); } })>
                    @if(survivalUnlocked)
                    {
                        <span>⚡</span>
                        <span class="mm-multiplayer-label">SURVIVAL MODE</span>
                    }
                    else
                    {
                        <span>🔒</span>
                        <span class="mm-multiplayer-label">Complete a run to unlock</span>
                    }
                </button>
            </div>
            @* =================== END =================== *@

       
                @* ------------------------- Tab buttons (right side) ------------------------- *@
                @* Tabs buttons — clicking sets _tab variable, .on class shows active state *@
                @* ------------------------- Tab buttons (right side) ------------------------- *@
                <div class="mm-tabs">

                    @* ---------------- Community tab ----------------- *@
                    <button class="mm-tab @(_tab == "community" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")"
                            onclick=@(() => { 
                                if(MainMenuTutorial.Instance?.IsActive != true) SetTab("community");
                                Sound.Play("UI_Button"); })>
                        💬 Community
                    </button>

                    @* ── Pet tab — always visible, locked state before 3 runs ── *@
                        <button class="mm-tab @(_tab == "farm" ? "on" : "") @(_save?.PetUnlockShown != true ? "mm-tab-locked" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")" 
                         onclick=@(() => { 
                            if(MainMenuTutorial.Instance?.IsActive != true) SetTab("farm");
                            Sound.Play("UI_Button"); Sound.Play("Meow_cat"); })>

                        @if(_save?.PetUnlockShown != true)
                        {
                            @("📦")
                            <span class="mm-tab-lock-hint">Unlocks after 3 runs</span>
                        }
                        else
                        {
                            @("🐾 Pet")
                        }
                    </button>

                    @* ── Leaderboard ── *@
                    <button class="mm-tab @(_tab == "lb" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")" 
                            onclick=@(() => { 
                                if(MainMenuTutorial.Instance?.IsActive != true) SetTab("lb");
                                  Sound.Play("UI_Button");  Sound.Play("leaderboard_tab"); })>
                        🏅 Leaderboard
                    </button>

                    @* ── Stats ── *@
                    <button class="mm-tab @(_tab == "stats" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")" 
                            onclick=@(() => { 
                                if(MainMenuTutorial.Instance?.IsActive != true) SetTab("stats");
                                  Sound.Play("UI_Button"); })>
                        📊 Stats
                    </button>

                    @* ── Shop — shows fragment count pill when player has frags ── *@
                    <button class="mm-tab @(_tab == "shop" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")" 
                            onclick=@(() => { if(MainMenuTutorial.Instance?.IsActive != true) SetTab("shop");
                              Sound.Play("UI_Button"); Sound.Play("shop_tab"); })>
                        💠 Shop
                        @if((_save?.TotalFragments ?? 0) > 0)
                        { 
                            <span class="mm-frags-pill">@(_save?.TotalFragments ?? 0)</span> 
                        }
                    </button>

                    @* ── Credits ── *@
                    <button class="mm-tab @(_tab == "credits" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")"
                        onclick=@(() => { if(MainMenuTutorial.Instance?.IsActive != true) SetTab("credits");
                        Sound.Play("UI_Button"); })>
                        🎖️ Credits
                    </button>
                </div>
            </div>
        </div>
    }
</root>

@code {
    const bool DEV_MODE = false; // set to false before publishing
    ChainReactionGame Game => ChainReactionGame.Instance;
    public static MainMenu Instance { get; private set; }

    // ------------------------- Main menu tuto -------------------------- //
    public void SetTabPublic(string tab) => SetTab(tab);
    public void SetShopTabPublic(string tab) { _shopTab = tab; StateHasChanged(); }
    public void StartGamePublic() => Game?.StartGame(GameMode.Wave, null);
    // ------------------------------------------------------------------- //
    PlayerSave _save;

    // useful for MainMenu Hud tuto (when buying Heart consumable)
    public void ReloadSave() 
    { 
        _save = PlayerSave.Load();
        _showWelcomeGift = false; // prevent OnUpdate from re-triggering welcome
        StateHasChanged();
    } 

    bool               _saveLoaded = false;
    Leaderboards.Board _board      = null;
    string             _tab        = "";
    bool               _loadingLb  = false;
    bool               _gameStarted = false;

    /* ── Idle grid state ─────────────────────────────────── */

    // Cell types: 0=empty, 1=chest, 2=gem, 3=bomb-chest, 4=bomb-placed, 5=blasting, 6=destroyed
    int[,] _cells     = new int[5,5];
    bool[,] _inBlast  = new bool[5,5];
    bool[,] _bombed   = new bool[5,5];

    enum IdlePhase { Idle, BombPlace, Detonate, ChestPop, Reward, Reset }
    IdlePhase _phase  = IdlePhase.Reset;
    float     _phaseTimer = 0f;
    float     _rollTimer  = 0f;

    // Bomb placement
    int _bombR = -1, _bombC = -1;
    List<(int r, int c)> _blastCells  = new();
    List<(int r, int c)> _hitChests   = new();
    int   _chestPopIdx   = 0;
    float _chestPopTimer = 0f;
    int   _fragsEarned   = 0;
    int   _todayFrags    = 0;
    int _displayTodayFrags = 0;

    // particles burst when clicking activate idle grid
    bool _justActivated   = false;
    float _activatedTimer = 0f;

    // Toggle button for idle farm (preview grid)
    bool _idleActive = false;

    // Leaerboard
    string _lbMode = "score"; // "score" or "pet"
    Leaderboards.Board _petBoard = null;

    // Popups
    class IdlePopup
    {
        public string Text;
        public string CssClass;
        public float  X, Y;
        public bool   Active;
        public float  Life;
    }
    List<IdlePopup> _popups = new();

    Random _rng = new Random();


    string _shopTab = "mods"; // default to modifiers

    // Survival Mode
    bool _showSurvivalLoadout = false;
    HashSet<GridManager.BombType> _survivalSelectedBombs = new();

        


    /* ── Save / LB ──────────────────────────────────────── */

    protected override void OnStart()
    {
        Instance = this;
        _save = PlayerSave.Load();

        
        // Safety — BombSlot2 is a permanent welcome gift, always ensure it's granted
        if(!_save.OwnsModifier(ModifierId.BombSlot2))
        {
            _save.OwnedModifiers.Add("BombSlot2");
            _save.OwnedBombs.Add(GridManager.BombType.Diagonal.ToString());
            _save.Save();
            Log.Info("StartGame — BombSlot2 was missing, re-granted");
        }

        _saveLoaded = true;
        _todayFrags = _save.GetTodayIdleFragments(); // load persisted value
        _displayTodayFrags = _todayFrags; // snap, don't animate from 0
        _idleActive = _save?.PassiveIdleGridActivated ?? false;

        // Welcome gift for first-time players
        if(!_save.WelcomeShown && !_welcomeGiftDismissed)
        {
            _showWelcomeGift = true;
            _giftStage = _save.WelcomeChestsOpened; // restore progress from disk

            for(int i = 0; i < _giftStage && i < _welcomeGifts.Count; i++)
                _welcomeGifts[i].Opened = true; // mark already-opened chests
                
            Sound.Play("fireworks");
            Sound.Play("gift");
            
            PlaySoundWithDelay(1000, "pearl_stars");
            PlaySoundWithDelay(1000, "crowd_2");
            PlaySoundWithDelay(2000, "applause_1");
        }
        // Welcome already done but tutorial not completed — resume tutorial
        else if(!_save.MenuTutorialCompleted)
        {
            _ = ActivateTutorialNextFrame();
        }

        // force both flags off on start - Pet 
        _showPetUnlock = false;
        _showEvolution = false;

        _ = LoadForceLb();

        ResetGrid();
        if(!_save.PetUnlockShown)
            SetTab("community"); // activates first tab button
        else
            SetTab("community");

        // Hardcode community content — update with each game release
        _community = new CommunityData
        {
            Welcome  = "Hey! Welcome to 10XPLOSION Early Access 🎉\n\nThanks for playing — you're one of the first.\n\nYour feedback shapes what gets built next.",
            Devnote  = "Working on balancing the game and fixing bugs this week. Drop ideas in Discord!",
            Discord  = "",
            Events   = new List<CommunityEvent>
            {
                new CommunityEvent { Badge="NEW",     Text="Tutorial system — now live!",              Color="green"  },
                new CommunityEvent { Badge="NEW",    Text="Finishing Pet interaction system + hand-made pixel art",  Color="green" },
                new CommunityEvent { Badge="NEW",    Text="SURVIVAL MODE IS LIVE (Alpha - needs adjustments from feedbacks)",  Color="green" },
                @* new CommunityEvent { Badge="SOON",    Text="", Color="purple" }, *@
                new CommunityEvent { Badge="PLANNED", Text="More bomb types + modifiers",              Color="gray"   },
            }
        };
            
        StateHasChanged();
    }
    
    float _explosionTimer = 0f; // menu visual vfx
    bool _petFrameToggle  = false;
    float _petFrameTimer = 0f;
    protected override void OnUpdate()
    {
        bool s = ChainReactionGame.Instance?.GameStarted ?? false;

        if ( s != _gameStarted )
        {
            _gameStarted = s;
            if ( !s )
            {
                _save = PlayerSave.Load();
                _todayFrags = _save.GetTodayIdleFragments();
                _displayTodayFrags = _todayFrags; // no animation on return, just snap

                   // Re-evaluate welcome state from save
                    if (_save.WelcomeShown)
                        _showWelcomeGift = false; // ← hard close if save says done
            }
            StateHasChanged();
        }

        if ( _gameStarted ) return;

        // PET ANIMATION
        _petFrameTimer -= Time.Delta;
        if (_petFrameTimer <= 0f)
        {
            _petFrameTimer = 0.4f;
            _petFrameToggle = !_petFrameToggle;
            StateHasChanged();
        }

        // PET NAME - TextField Entry
        if(_editingPetName && _nameEntry != null)
        {
            string current = _nameEntry.Text ?? "";
            if(current != _petNameInput)
            {
                _petNameInput = current;
                StateHasChanged();
            }
        }

        // ------------- FAKE BOMBS - Main Menu -------------- //
        _explosionTimer -= Time.Delta;
        if (_explosionTimer <= 0f)
        {
            _explosionTimer = 1.5f + (float)_rng.NextDouble() * 3f; // every 1.5-4.5s
            TriggerMenuExplosion();
            StateHasChanged();
        }  
        bool explosionChanged = false;
        for (int i = _menuExplosions.Count - 1; i >= 0; i--)
        {
            var e = _menuExplosions[i];
            _menuExplosions[i] = (e.x, e.y, e.life - Time.Delta, e.emoji);
            if (_menuExplosions[i].life <= 0f) { _menuExplosions.RemoveAt(i); explosionChanged = true; }
            else explosionChanged = true;
        }
        if (explosionChanged) StateHasChanged();
        // ---------------------------------------------------- // 

        // Idle grid farm pet
        TickIdleGrid();

        // Check if THREE RUN DONE for PET UNLOCK
        // Only check after welcome gift is fully done
        if(!_showWelcomeGift 
        && !_showPetUnlock
        && !_showEvolution
        && (_save?.TotalRuns ?? 0) >= 3
        && !(_save?.PetUnlockShown ?? false))
        {
            _ = TriggerPetUnlockCinematic();
        }

        // Check evolution
        CheckPetEvolution();
    }

    async System.Threading.Tasks.Task ActivateTutorialNextFrame()
    {
        // Wait one frame for MainMenuTutorial.Instance to register itself
          await GameTask.DelaySeconds(0.1f);
    
        // Make sure welcome overlay is hidden when resuming tutorial (i.e: if resuming after a crash)
        _showWelcomeGift = false;
        _welcomeGiftDismissed = true; // hard lock
        _giftStage = _welcomeGifts.Count; // mark all as opened
        foreach(var g in _welcomeGifts) g.Opened = true;
        
        MainMenuTutorial.Instance?.Activate(_save);
        StateHasChanged();
    }

    string GetPetImagePath() => PetVisuals.GetImagePath(
        _save?.PetLevel ?? 1,
        _save?.PetUnlockShown ?? false,
        _petFrameToggle
    );

    async System.Threading.Tasks.Task TriggerPetUnlockCinematic()
    {
        _showPetUnlock = true;
        _petUnlockChestPhase = true;
        _showEvolution = true;
        _evoShowNew = false;
        _evoText = "Something is calling out to you...";
        _evoPhase = "evo-text";
        Sound.Play("pearl_stars");
        Sound.Play("Evolution_Pet");

        MusicManager.Instance?.StopMenuMusic();

        StateHasChanged();

        await GameTask.DelaySeconds(3.0f);

        _evoText = "A mysterious chest appeared!";
        _evoPhase = "evo-start";
        StateHasChanged();

        await GameTask.DelaySeconds(3.0f);

        _evoText = "It's trembling...";
        _evoPhase = "evo-shake";
        StateHasChanged();
        Sound.Play("evo_rumble");

        await GameTask.DelaySeconds(2.5f);

        // Flash — chest becomes egg
        _evoText = "";
        _evoPhase = "evo-flash";
        _petUnlockChestPhase = false; // switch to egg
        _evoShowNew = false;
        StateHasChanged();

        await GameTask.DelaySeconds(1.0f);

        _evoPhase = "evo-reveal";
        _evoText = "A mysterious egg appeared!";
    
        StateHasChanged();
    }

    /* ── Idle grid tick ─────────────────────────────────── */

    void TickIdleGrid()
    {
        if(!_idleActive) return; 

        // If idle cap reached, force stop and don't tick
        int maxIdle = _save?.MaxIdleFragsPerDay ?? 80;
        if(_save != null && _save.GetTodayIdleFragments() >= maxIdle)
        {
            _idleActive = false;
            if(_save != null) 
            { 
                _save.PassiveIdleGridActivated = false; 
                _save.Save(); 
            }
            StateHasChanged();
            return;
        }

        _rollTimer += Time.Delta;

        // Burst particles when toggling
        if(_activatedTimer > 0)
        {
            _activatedTimer -= Time.Delta;
            if(_activatedTimer <= 0) 
            { 
                _justActivated = false; 
                StateHasChanged(); 
            }
        }

        // Tick popups
        bool popupChanged = false;
        for(int i = _popups.Count - 1; i >= 0; i--)
        {
            _popups[i].Life -= Time.Delta;
            if(_popups[i].Life <= 0) { _popups.RemoveAt(i); popupChanged = true; }
        }

        switch(_phase)
        {
            case IdlePhase.Reset:
                ResetGrid();
                _phase = IdlePhase.Idle;
                _phaseTimer = 1.5f;
                StateHasChanged();
                break;

            case IdlePhase.Idle:
                _phaseTimer -= Time.Delta;
                if(_phaseTimer <= 0)
                {
                    PlaceBomb();
                    _phase = IdlePhase.BombPlace;
                    _phaseTimer = 0.8f;
                    StateHasChanged();
                }
                break;

            case IdlePhase.BombPlace:
                _phaseTimer -= Time.Delta;
                if(_phaseTimer <= 0)
                {
                    Detonate();
                    _phase = IdlePhase.Detonate;
                    _phaseTimer = 0.6f;
                    StateHasChanged();
                }
                break;

            case IdlePhase.Detonate:
                _phaseTimer -= Time.Delta;
                if(_phaseTimer <= 0)
                {
                    _chestPopIdx = 0;
                    _chestPopTimer = 0f;
                    _phase = IdlePhase.ChestPop;
                    StateHasChanged();
                }
                break;

            case IdlePhase.ChestPop:
                _chestPopTimer -= Time.Delta;
                if(_chestPopTimer <= 0 && _chestPopIdx < _hitChests.Count)
                {
                    var (r,c) = _hitChests[_chestPopIdx];
                    _cells[r,c] = 6; // destroyed
                    SpawnPopup(r, c, _chestPopIdx);
                    _chestPopIdx++;
                    _chestPopTimer = 0.25f;
                    StateHasChanged();
                }
                else if(_chestPopIdx >= _hitChests.Count && _chestPopTimer <= 0)
                {
                    _phase = IdlePhase.Reward;
                    _phaseTimer = 1.5f;
                    StateHasChanged();
                }
                break;

            case IdlePhase.Reward:
                _phaseTimer -= Time.Delta;
                if(_phaseTimer <= 0)
                {
                    _phase = IdlePhase.Reset;
                    _rollTimer = 0f;
                    StateHasChanged();
                }
                break;
        }

        if(popupChanged) StateHasChanged();
    }

    void ResetGrid()
    {
        for(int r=0;r<5;r++)
            for(int c=0;c<5;c++)
                _cells[r,c] = 0;
        _inBlast = new bool[5,5];
        _bombed  = new bool[5,5];
        _blastCells.Clear();
        _hitChests.Clear();
        _chestPopIdx = 0;
        _fragsEarned = 0;
        _bombR = -1; _bombC = -1;

        // Randomly place 4-7 chests
        var empties = new List<(int,int)>();
        for(int r = 0; r < 5; r++) 
            for(int c = 0; c < 5; c++) 
                empties.Add((r,c));
         
        Shuffle(empties);

        int count = _rng.Next(5, 9); // was 4-7, now 5-8 — denser

        // Spawn chances
        for(int i = 0; i < count && i < empties.Count; i++)
        {
            var (r,c) = empties[i];
            int roll = _rng.Next(100);
            // was: roll < 70 chest, < 90 gem, else bomb-chest
            // now: more bomb chests for chain reactions in preview
            _cells[r,c] = roll < 55 ? 1 : roll < 78 ? 2 : 3;
        }
    }

    void PlaceBomb()
    {
        // Find empty cell adjacent to the most chests — makes chains more likely
        var empties = new List<(int r, int c)>();
        for(int r = 0; r < 5; r++)
            for(int c = 0; c < 5; c++)
                if(_cells[r,c] == 0) empties.Add((r,c));
                
        if(empties.Count == 0) return;

        // Score each empty cell by how many chests are adjacent
        (int r, int c) best = empties[0];
        int bestScore = -1;

        // adjacent coords 
        int[] dr = {-1,1,0,0};
        int[] dc = {0,0,-1,1};

        foreach(var (er, ec) in empties)
        {
            int score = 0;
            for(int d = 0; d < 4; d++)
            {
                int nr = er + dr[d];
                int nc = ec + dc[d];

                if((nr >= 0 && nr < 5) && (nc >= 0 && nc < 5) && (_cells[nr,nc] >= 1))
                    score++;
            }
            // Add some randomness so it's not always identical
            score += _rng.Next(3);
            if(score > bestScore) { bestScore = score; best = (er, ec); }
        }
        (_bombR, _bombC) = best;
        _cells[_bombR, _bombC] = 4;
    }

    void Detonate()
    {
        if(_bombR < 0) return;
        _blastCells.Clear();
        _hitChests.Clear();

        var fired = new HashSet<(int,int)>();
        var queue = new Queue<(int,int)>();
        
        // seed with the placed bomb
        fired.Add((_bombR, _bombC));
        queue.Enqueue((_bombR, _bombC));
        _cells[_bombR, _bombC] = 5; // blasting

        int[] dr = {-1, 1, 0, 0, 0};
        int[] dc = { 0, 0,-1, 1, 0};

        while(queue.Count > 0)
        {
            var (br, bc) = queue.Dequeue();

            for(int d = 0; d < 5; d++)
            {
                int nr = br + dr[d];
                int nc = bc + dc[d];
                if(nr < 0 || nr >= 5 || nc < 0 || nc >= 5) continue;

                if(!_blastCells.Contains((nr, nc)))
                    _blastCells.Add((nr, nc));
                _inBlast[nr, nc] = true;

                int cell = _cells[nr, nc];

                // chest types 1/2/3 — hit
                if(cell >= 1 && cell <= 3 && !_hitChests.Contains((nr, nc)))
                    _hitChests.Add((nr, nc));

                // bomb chest (type 3) triggers chain — queue it if not already fired
                if(cell == 3 && !fired.Contains((nr, nc)))
                {
                    fired.Add((nr, nc));
                    _cells[nr, nc] = 5; // also blasting
                    queue.Enqueue((nr, nc));
                }
            }
        }

        // Calculate reward
        int hits = _hitChests.Count;

        // Convert hits to fragments earned
        _fragsEarned = hits == 0 ? 0
                    : hits <= 2 ? 1
                    : hits <= 4 ? _rng.Next(1, 3)
                    : hits <= 6 ? _rng.Next(2, 4)
                    :              _rng.Next(3, 6); // max 5 frags


        // ── Daily cap check ──
        int maxIdle = _save?.MaxIdleFragsPerDay ?? 80; // 

        // Check daily cap BEFORE adding fragments
        int currentDailyFrags = _save?.GetTodayIdleFragments() ?? 0;
        
        if(currentDailyFrags >= maxIdle)
        {
            _fragsEarned = 0;
            _idleActive = false; // auto-stop
            _save.PassiveIdleGridActivated = false;
            _save.Save();
            SpawnCenteredMsg("💤 Pet is tired — come back tomorrow", "idle-msg-mega");
            StateHasChanged();
            return;
        }
        
        // Clamp fragments to not exceed cap
        // note: When I build the pet upgrade system later, 
        // I'll just have to increase PetLevel in the save and the cap scales automatically.
        int remaining = maxIdle - currentDailyFrags;

        if(_fragsEarned > remaining) _fragsEarned = remaining;

        if(_hitChests.Count == 0)
        {
            SpawnCenteredMsg("No chests hit...", "idle-msg-neutral");
        }
        else if(_fragsEarned >= 15)
        {
            SpawnCenteredMsg($"💥 MEGA CHAIN! +{_fragsEarned} 💠", "idle-msg-mega");
        }
        else if(_fragsEarned >= 8)
        {
            SpawnCenteredMsg($"🔥 Chain! +{_fragsEarned} 💠", "idle-msg-chain");
        }
        else if(_fragsEarned >= 3)
        {
            SpawnCenteredMsg($"✨ +{_fragsEarned} 💠 nice!", "idle-msg-nice");
        }
        else
        {
            SpawnCenteredMsg($"+{_fragsEarned} 💠", "idle-msg-small");
        }

        //TODO: Add sound 
        if(_fragsEarned > 0)
        {
            // Reward chime — use a short positive sound asset
            Sound.Play("fragments_coins");
            // Or trigger through GridRenderer if you have one accessible

            _save?.AddIdleFragments(_fragsEarned);
            _todayFrags = _save?.GetTodayIdleFragments() ?? 0; // sync display target
            StateHasChanged(); // shop pill + left stats refresh
        }
    }

    // grid pop up msg
    void SpawnCenteredMsg(string text, string css)
    {
        // Dead center of the grid = 50%, slightly above middle = 40%
        _popups.Add(new IdlePopup {
            Text = text, CssClass = css,
            X = 50f, Y = 40f,
            Active = true, Life = 2.2f
        });
    }

   void SpawnPopup(int r, int c, int idx)
    {
        // Convert grid cell to % position within mm-grid-preview-wrap
        // Cell size 106px + gap 12px = 118px step, grid total 578px
        float cellStep = 118f;
        float gridSize = 578f;
        float xPct = (c * cellStep + 53f) / gridSize * 100f;
        float yPct = (r * cellStep + 53f) / gridSize * 100f;

        string text;
        string css;

        if(idx == _hitChests.Count - 1 && _fragsEarned > 0)
        {
            if(_fragsEarned >= 15)      { text = $"MEGA! +{_fragsEarned}💠"; css = "popup-mega"; }
            else if(_fragsEarned >= 8)  { text = $"CHAIN! +{_fragsEarned}💠"; css = "popup-chain"; }
            else if(_fragsEarned >= 3)  { text = $"+{_fragsEarned}💠 NICE!"; css = "popup-nice"; }
            else                        { text = $"+{_fragsEarned}💠"; css = "popup-small"; }
        }
        else
        {
            string[] smalls = { "💥", "✓", "Gone!", "💣" };
            text = smalls[_rng.Next(smalls.Length)];
            css = "popup-hit";
        }

        _popups.Add(new IdlePopup {
            Text = text, CssClass = css,
            X = xPct, Y = yPct,
            Active = true, Life = 1.8f
        });
    }

    void Shuffle<T>(List<T> list)
    {
        for(int i=list.Count-1;i>0;i--)
        { int j=_rng.Next(i+1); (list[i],list[j])=(list[j],list[i]); }
    }

    

    /* ── Cell rendering helpers ─────────────────────────── */

    string GetLiveCellClass(int r, int c) => _cells[r,c] switch {
        1 => "ct-chest",
        2 => "ct-gem",
        3 => "ct-bomb-chest",
        4 => "ct-bomb",
        5 => "ct-blast",
        6 => "ct-destroyed",
        _ => "ct-empty"
    };

    string GetLiveAnimClass(int r, int c)
    {
        if(_cells[r,c] == 5) return "ca-detonate";
        if(_cells[r,c] == 6) return "ca-destroy";
        if(_inBlast[r,c] && _cells[r,c] == 0) return "ca-blast-empty";
        return "";
    }

    string GetLiveIcon(int r, int c) => _cells[r,c] switch {
        1 => "🎁",
        2 => "💎",
        3 => "💣",
        4 => "💥",
        _ => ""
    };

    /* ── Play / tabs / LB ───────────────────────────────── */

    void OnPlay() => Game?.StartGame(GameMode.Wave, null);

    void SetTab(string tab)
    {
        _tab = tab; //_tab == tab ? "" : tab; <- this was allowing toggling (visible/invisible) on same tab btn
        StateHasChanged();
        if(_tab == "lb")
        {
            if(_lbMode == "score")       _ = LoadLb();
            else if(_lbMode == "pet")    _ = LoadPetLb();
            else if(_lbMode == "chain")  _ = LoadChainLb();
            else if(_lbMode == "force")  _ = LoadForceLb();
        }

        // Community
       // if (_tab == "community") _ = LoadCommunity();
    }

    async System.Threading.Tasks.Task LoadLb()
    {
        _loadingLb = true; 
        _board = null; 
        StateHasChanged();

        try
        {
            var b = Leaderboards.Get("chain_reaction_score");
            if(b != null) { b.MaxEntries = 10; b.Group = "global"; await b.Refresh(); _board = b; }
        }
        catch(Exception ex) { Log.Warning($"LB: {ex.Message}"); }
        _loadingLb = false; StateHasChanged();
    }

    string GetTimePlayed()
    {
        float m = _save?.TotalMinutesPlayed ?? 0f;
        return m < 60f ? $"{(int)m}m" : $"{(int)(m/60f)}h {(int)(m%60f)}m";
    }

    protected override int BuildHash()
    {
        int a = System.HashCode.Combine(_tab, _saveLoaded, _loadingLb,
            _board?.Entries?.Length ?? 0, _gameStarted,
            _phase, _chestPopIdx, _popups.Count);

        int b = System.HashCode.Combine(
            _save?.ActiveModifiers?.Count ?? 0,
            _save?.OwnedModifiers?.Count ?? 0,
            _save?.OwnedBombs?.Count ?? 0,
            _save?.TotalConsumablesOwned ?? 0);

        int c = System.HashCode.Combine(
        _editingPetName,
        _showPetUnlock,
        _showEvolution,
        _evoPhase,
        _evoText,
        _discordCopied);

        int d = System.HashCode.Combine(
        _save?.TotalFragments ?? 0,   
        _save?.PetLevel ?? 1,
        _save?.PetUnlockShown ?? false,
        _musicLinkCopied, _menuExplosions.Count , _petFrameToggle);
        
        return a ^ b ^ c ^ d;
    }
    // --------------------------------------------------------------------------- //
    // -------------------------- Toggle Idle Farm Grid -------------------------- // 
    // --------------------------------------------------------------------------- //

    void OnToggleIdle()
    {
        _idleActive = !_idleActive;

        // play burst particle (removed bc it sucks for now)
        @* if(_idleActive)
        {
            _justActivated  = true;
            _activatedTimer = 1.2f;
            SpawnActivateBurst();
        } *@

        StateHasChanged();
    }

    void SpawnActivateBurst()
    {
        // Spawn a ring of centered popups that fly outward
        string[] words = { "💠", "✨", "💠", "✨", "💠" };
        float[] xs = { 35f, 42f, 50f, 58f, 65f };
        float[] ys = { 55f, 45f, 40f, 45f, 55f };
        for(int i = 0; i < words.Length; i++)
        {
            float xi = xs[i], yi = ys[i];
            int   ii = i;
            _popups.Add(new IdlePopup {
                Text     = words[ii],
                CssClass = "popup-chain",
                X        = xi,
                Y        = yi,
                Active   = true,
                Life     = 1.4f
            });
        }
    }

    // --------------------------------------------------------------------------- //
    // -------------------------- SHOP BUTTONS on Shop -------------------------- // 
    // --------------------------------------------------------------------------- //
    void OnBuyMod(ModifierId id, int cost)
    {
        if(_save == null) return;
        bool bought = _save.TryBuyModifier(id, cost);
        if(bought) StateHasChanged();
        Sound.Play("BuyShop");
    }

    void OnToggleMod(ModifierId id)
    {
        _save?.ToggleModifier(id);
        StateHasChanged();
    }

    // --------------------------------------------------------------------------- //
    // ------------------------------- DEBUG BUTTONS  ---------------------------- // 
    // --------------------------------------------------------------------------- //

    void OnWipeSave()
    {
        PlayerSave.DEBUG_WipeSave();
        _save = PlayerSave.Load();
        _todayFrags = _save.GetTodayIdleFragments();
        _displayTodayFrags = _todayFrags;

        // Reset all welcome gift in-memory state
        _showWelcomeGift = false;
        _welcomeGiftDismissed = false;
        _giftStage = 0;
        foreach(var g in _welcomeGifts) g.Opened = false;

        // Reset tutorial
        MainMenuTutorial.Instance?.ForceReset();

        // Mirror OnStart — re-show welcome if fresh save
        if(!_save.WelcomeShown)
        {
            _giftStage = 0; // always 0 on fresh wipe
            _showWelcomeGift = true;
            Sound.Play("fireworks");
            Sound.Play("gift");
            PlaySoundWithDelay(1000, "pearl_stars");
        }

        StateHasChanged();
    }
    void OnAddFrags()
    {
        if(_save == null) return;
        _save.TotalFragments += 1000;
        _save.Save();
        StateHasChanged();
    }

    void OnUnlockAll()
    {
        if(_save == null) return;
        foreach(var mod in ShopCatalog.All)
        {
            if(!_save.OwnsModifier(mod.Id))
            {
                _save.OwnedModifiers.Add(mod.Id.ToString());
            }
        }
        _save.Save();
        StateHasChanged();
    }

    // ------------------------------------------------------------------------ //
    // ----------------------- GIFTS SPECIALS SECTION ------------------------- //
    // ------------------------------------------------------------------------ //
    bool _showWelcomeGift = false;
    int _giftStage = 0; // 0=intro, 1=chest1 open, 2=chest2 open, 3=chest3 open, 4=done

    class GiftReward
    {
        public string ChestIcon { get; set; } = "🎁";
        public string GiftIcon  { get; set; } = "";
        public string Title     { get; set; } = "";
        public string Desc      { get; set; } = "";
        public bool   Opened    { get; set; } = false;
    }

    List<GiftReward> _welcomeGifts = new()
    {
        new GiftReward { 
            GiftIcon = "💣💣", 
            Title = "Twin Loader", 
            Desc = "+1 bomb slot, permanent" 
        },
        new GiftReward { 
            GiftIcon = "🎯", 
            Title = "Sniper Trial", 
            Desc = "Sniper bombs for 3 runs" 
        },
        new GiftReward { 
            GiftIcon = "✕", 
            Title = "Diagonal Trial", 
            Desc = "+1 bomb type, permanent" 
        },
    };

    void OnOpenChest(int idx)
    {
        if(idx >= _welcomeGifts.Count) return;
        if(_welcomeGifts[idx].Opened) return;

        // Play open chest, then last chest if last gift in the list
        Sound.Play(idx == _welcomeGifts.Count - 1 ? "open_last_chest" : "open_chest_1");
        Sound.Play("Chest_open");

        //Log.Warning($"OPEN CHEST : {idx}");
        _welcomeGifts[idx].Opened = true;
        _giftStage = idx + 1;

        //mark as shown when last chest opened, don't wait for button click
        if(_giftStage >= _welcomeGifts.Count)
        {
            _save.WelcomeShown = true;
        }
        
        // Persist progress so closing the game mid-flow doesn't reset
        _save.WelcomeChestsOpened = _giftStage;
        _save.Save();
        
        StateHasChanged();
    }

    bool _welcomeGiftDismissed = false; // set once, never resets during session
    void OnCloseWelcome()
    {
        _showWelcomeGift = false;
        _welcomeGiftDismissed = true; // hard lock for this session
        _save.WelcomeShown = true;
        _save.WelcomeChestsOpened = _welcomeGifts.Count;
        _save.OwnedModifiers.Add("BombSlot2");  // grant the modifier
        _save.OwnedBombs.Add(GridManager.BombType.Diagonal.ToString());
        _save.TrialBombsRunsLeft = 3;             // grant trial runs
        _save.Save();

        // Start menu tutorial after welcome gifts close
        MainMenuTutorial.Instance?.Activate(_save);

        Sound.Play("Pop_Button_1");

        StateHasChanged();
    }
    
    async void PlaySoundWithDelay(int delay, string soundName)
    {
        await Task.Delay(delay);

        Sound.Play(soundName);
    }

    // ------------------------------------------------------------------------ //
    // -------------------------- BOMBS SHOP SECTION -------------------------- //
    // ------------------------------------------------------------------------ //
    void OnBuyBomb(GridManager.BombType type, int cost)
    {
        if (_save == null) return;
        bool bought = _save.TryBuyBomb(type, cost);
        if (bought)
        {
            Sound.Play("BuyShop"); // or a dedicated purchase sound
            Sound.Play("climax_explosion_lv5");
            StateHasChanged();
        }
    }

    // ------------------------------------------------------------------------ //
    // ---------------------- Consumables SHOP SECTION ------------------------ //
    // ------------------------------------------------------------------------ //
    void OnBuyConsumable(ConsumableId id, int cost)
    {
        if (_save == null) return;
        if ((_save.TotalConsumablesOwned) >= 3) return; // cap check
        bool bought = _save.TryBuyConsumable(id, cost);
        if (bought)
        {
            Sound.Play("BuyShop");
            StateHasChanged();
            Log.Warning(_save.TotalConsumablesOwned);
            
            // Notify tutorial
            if (id == ConsumableId.Heart)
            {
                MainMenuTutorial.Instance?.OnHeartPurchased();
                Sound.Play("");
            }
            else if (id == ConsumableId.ChainNuke)
                Sound.Play("usenukebomb");
            
            else if(id == ConsumableId.FuseExtension)
                Sound.Play("usetimeup");
            
            else if(id == ConsumableId.ColumnSweep)
                Sound.Play("usesweep");

            else if(id == ConsumableId.Overdrive)
                Sound.Play("useoverdrive");

            else if(id == ConsumableId.Overload)
                Sound.Play("Whoosh_bubble");

            else if(id == ConsumableId.Redraw)
                Sound.Play("useredraw");
            
            else if(id == ConsumableId.Reveal)
                Sound.Play("usereveal");

            else if(id == ConsumableId.ClusterDrop)
                Sound.Play("useclusterdrop");
         
        }
    }

    // ------------------------------------------------------------------------ //
    // ------------------------------ PET SYSTEM ------------------------------ //
    // ------------------------------------------------------------------------ //
    string GetPetEmoji() => (_save?.PetUnlockShown == true)
    ? (_save?.PetLevel ?? 1) switch
    {
        1 => "🥚",
        2 => "🐣",
        3 => "🐥",
        4 => "🦅",
        _ => "🐉"
    }
    : "📦";

    // Shows what comes AFTER current level — reads current level correctly
    string GetNextPetEmoji() => (_save?.PetLevel ?? 1) switch
    {
        1 => "🐣",
        2 => "🐥",
        3 => "🦅",
        4 => "🐉",
        _ => "🐉"     // level 5 = no next
    };

    // Shows the form BEFORE evolution — uses _evoFromLevel, not save
    string GetPrevPetEmoji() => _evoFromLevel switch
    {
        1 => "🥚",
        2 => "🐣",
        3 => "🐥",
        4 => "🦅",
        _ => "🐉"   // fallback to dragon
    };
    string GetNextPetName() => (_save?.PetLevel ?? 1) switch
    {
        1 => "Mystery Egg",
        2 => "Hatchling",
        3 => "Sparky",
        4 => "Storm",
        5 => "Inferno",
        6 => "Sovereign",        
        _ => ""
    };

   string GetPetName() => (_save?.PetLevel ?? 1) switch
    {
        1 => "Mystery Egg",
        2 => "Hatchling",
        3 => "Sparky",
        4 => "Storm",
        5 => "Inferno",
        6 => "Sovereign",
        _ => "Sovereign"
    };

    float GetPetXpPct()
    {
        int needed = GetPetXpNeeded();
        int current = _save?.PetXp ?? 0;
        return MathF.Min(100f, (current / (float)needed) * 100f);
    }

    int GetPetXpNeeded() => PlayerSave.GetXpNeeded(_save?.PetLevel ?? 1);

    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // ---------- pet (next) handle gift and evolution animation -------
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    // ── Pet naming ──
    bool   _editingPetName = false;
    string _petNameInput   = "";

    // ── First run pet unlock popup ──
    bool _showPetUnlock = false;
    bool _petUnlockChestPhase = false; // showing chest before egg


    // ── Evolution cinematic ──
    bool   _showEvolution = false;
    string _evoPhase      = "";   // "evo-start" → "evo-shake" → "evo-flash" → "evo-reveal"
    bool   _evoShowNew    = false;


    // Returns custom name if set, otherwise default 
    string GetDisplayPetName()
    {
        string custom = _save?.PetCustomName ?? "";
        return string.IsNullOrWhiteSpace(custom) ? GetPetName() : custom;
    }

    void OnConfirmPetName()
    {
        if(_save == null) return;
        string trimmed = (_nameEntry?.Text ?? "").Trim();
        if(trimmed.Length == 0) trimmed = GetPetName();
        _save.PetCustomName = trimmed;
        _save.Save();
        _editingPetName = false;
        CloseNameEntry();
        StateHasChanged();
    }

    // ── First run pet unlock ──
    void OnClosePetUnlock()
    {
        _showPetUnlock = false;
        _save.PetUnlockShown = true;
        _save.Save();
        SetTab("farm"); // take them straight to pet tab
        Sound.Play("UI_Button");
        Sound.Play("Meow_cat");
        StateHasChanged();
    }

    // ── Call this from OnUpdate, after TickIdleGrid ──
    // Check if pet just leveled up this session
    int _evoFromLevel = 1;
    void CheckPetEvolution()
    {
        if(_save == null) return;
        if(_save.PendingEvolution && !_showEvolution)
        {
            _evoFromLevel = _save.PetLevel - 1; // only here
            Log.Info($"Evo: from {_evoFromLevel} to {_save.PetLevel}");
            _save.PendingEvolution = false;
            _save.Save();
            TriggerEvolution();
        }
    }

    // ── Kicks off the evolution sequence ──
    string _evoText = "";
    async void TriggerEvolution()
    {
        _showEvolution = true;
        _evoShowNew    = false;
        _evoText       = "";   

        MusicManager.Instance?.StopMenuMusic();

        // Phase 1 — dramatic text appears
        _evoPhase = "evo-text";
        _evoText = _evoFromLevel switch {
            1 => "Wait... what's happening?!",
            2 => "Huh.. What.. again?!",
            3 => "No way it could be that again..!",
            4 => "The pressure of the air suddenly changed..",
            _ => ""
        };

        Sound.Play("Evolution_Pet");
        StateHasChanged();
        await GameTask.DelaySeconds(3.0f);

        // Phase 2 — text changes, egg wobbles
             _evoText = _evoFromLevel switch {
            1 => "The egg is trembling...",
            2 => "Something stirs within...",
            3 => "A surge of energy!",
            4 => "I can sense it's.. warmth..",
            _ => ""
        };
        _evoPhase = "evo-start";
        StateHasChanged();
        await GameTask.DelaySeconds(3.0f);

        // Phase 3 — faster shake, tension builds
            _evoText = _evoFromLevel switch {
            1 => "A tiny crack appears!",
            2 => "It's growing fast!",
            3 => "Power is building...",
            4 => "Something legendary emerges!",
            _ => ""
        };
        _evoPhase = "evo-shake";
        StateHasChanged();
        await GameTask.DelaySeconds(2.5f);

        // Phase 4 — white flash, swap to new form
        _evoText    = "";
        _evoPhase   = "evo-flash";
        _evoShowNew = true;
        //Sound.Play("evo_flash");
        StateHasChanged();
        await GameTask.DelaySeconds(1.0f);

        // Phase 5 — reveal with fanfare
        _evoPhase = "evo-reveal";
        _evoText  = $"{GetDisplayPetName()} evolved!";
        StateHasChanged();
    }

    void OnCloseEvolution()
    {
        _showEvolution = false;
        _showPetUnlock = false;
        _petUnlockChestPhase = false;
        _evoPhase = "";
        _evoShowNew = false;

        MusicManager.Instance?.PlayMenuMusic(); // just play music back
        Sound.Play("UI_Button");

        // If this was the pet unlock cinematic, mark it done
        if(_save != null && !_save.PetUnlockShown)
        {
            _save.PetUnlockShown = true;
            _save.Save();
            SetTab("farm");
        }

        _save?.Save();
        StateHasChanged();
    }


////////////////////// TEXT ENTRY FIELD FOR PET NAME ///////////////////////

    // Add this field
    TextEntry _nameEntry;

    // Call this when _editingPetName becomes true
    void OpenNameEntry()
    {
        _nameEntry?.Delete();
        _nameEntry = null;

        // Wait one frame for the container div to exist in DOM
        _ = AttachNameEntryNextFrame();
    }

    Panel _nameEntryContainer;
    async System.Threading.Tasks.Task AttachNameEntryNextFrame()
    {
        await GameTask.DelaySeconds(0.05f);

        // Find the container we stamped with @ref
        var container = _nameEntryContainer;
        if(container == null)
        {
            Log.Warning("Name entry container not found");
            return;
        }

        _nameEntry = container.AddChild<TextEntry>();
        _nameEntry.AddClass("mm-pet-name-input");
        _nameEntry.MaxLength = 16;
        _nameEntry.Text = GetDisplayPetName();
        _nameEntry.AcceptsFocus = true;
        _nameEntry.Style.PointerEvents = PointerEvents.All;

        await GameTask.DelaySeconds(0.1f);
        _nameEntry?.Focus();
        Log.Info($"Name entry focused: {_nameEntry?.HasFocus}");
    }

    void CloseNameEntry()
    {
        _nameEntry?.Delete();
        _nameEntry = null;
    }
    
    // Leaderboard
    async System.Threading.Tasks.Task LoadPetLb()
    {
        _loadingLb = true;
        _petBoard = null;
        StateHasChanged();

        try
        {
            var b = Leaderboards.Get("chain_reaction_pet");
            if(b != null) { b.MaxEntries = 100; b.Group = "global"; await b.Refresh(); _petBoard = b; }
        }
        catch(Exception ex) { Log.Warning($"Pet LB: {ex.Message}"); }
        _loadingLb = false;
        StateHasChanged();
    }

    // ---------------------------------------------------- //
    //     Leaderboard - Sequence top chain reaction guy 
    // ---------------------------------------------------- //


    Leaderboards.Board _chainBoard = null;

    async System.Threading.Tasks.Task LoadChainLb()
    {
        _loadingLb = true;
        _chainBoard = null;
        StateHasChanged();

        try
        {
            var b = Leaderboards.Get("chain_reaction_chain");
            if(b != null) 
            { 
                b.MaxEntries = 100; b.Group = "global"; 
                await b.Refresh();

                _chainBoard = b; 
            }
        }
        catch(Exception ex) 
        { 
            Log.Warning($"Chain LB: {ex.Message}"); 
        }
        _loadingLb = false;
        StateHasChanged();
    }

        string GetChainTitle(int chainLen) => chainLen switch {
        >= 20 => "💀 God of Destruction",
        >= 15 => "🔥 Chain Sovereign",
        >= 12 => "⚡ Storm Caller",
        >= 10 => "💣 Demolition King",
        >= 8  => "🎯 Precision Bomber",
        >= 6  => "🔗 Chain Breaker",
        >= 4  => "💥 Detonator",
        _     => "🎁 Chest Hunter"
    };

    /////////////////////////////////////////////////////////////////////////
    // ----------------- 100 TOP CONTRIBUTOR XP BAR ---------------------- //
    /////////////////////////////////////////////////////////////////////////
    // Force leaderboard fields
    Leaderboards.Board _forceBoard     = null;
    long               _forceTotalScore = 0;
    long               _forceTarget     = 1_000_000_000L; // 1 billion
    int                _forceMedalTier  = 0; // 0=none, 1=bronze, 2=silver, 3=gold
    bool               _forceBarLoaded  = false;

    async System.Threading.Tasks.Task LoadForceLb()
    {
        _loadingLb   = true;
        _forceBoard  = null;
        StateHasChanged();

        try
        {
            var b = Leaderboards.Get("chain_reaction_score");
            if(b != null)
            {
                b.MaxEntries = 100;
                b.Group      = "global";
                await b.Refresh();
                _forceBoard = b; // for force board (in tab)
                _board = b; // for hall of fame (left side screen)

                // Sum all entries
                _forceTotalScore = 0;
                foreach(var entry in b.Entries)
                    _forceTotalScore += (long)entry.Value;

                // Subtract baseline so bar resets visually after each fill
                long baseline = _save?.ForceBaseline ?? 0L;
                long adjusted = _forceTotalScore - baseline;

                // Detect fill — how many times has it filled since baseline?
                long fills = adjusted / _forceTarget;
                if(fills > (_save?.ForceFillCount ?? 0))
                {
                    // Bar just filled — award fragments if in top 10
                    bool inTop10 = b.Entries.Take(10).Any(e => e.Me);
                    if(inTop10 && _save != null)
                    {
                        _save.TotalFragments += 500;
                        _save.ForceMedalTier = (int)MathF.Max(_save.ForceMedalTier, GetMedalTierForRank(b));
                        _save.ForceFillCount = (int)fills;
                        _save.Save();
                    }
                }

                // Compute local player medal tier for display
                _forceMedalTier = _save?.ForceMedalTier ?? 0;
                _forceBarLoaded = true;
            }
        }
        catch(Exception ex) { Log.Warning($"Force LB: {ex.Message}"); }

        _loadingLb = false;
        StateHasChanged();
    }

    int GetMedalTierForRank(Leaderboards.Board b)
    {
        for(int i = 0; i < b.Entries.Length; i++)
        {
            if(!b.Entries[i].Me) continue;
            if(i < 3)  return 3; // gold
            if(i < 10) return 2; // silver
            return 1;            // bronze
        }
        return 0;
    }

    float GetForceBarPct()
    {
        long baseline = _save?.ForceBaseline ?? 0L;
        long adjusted = _forceTotalScore - baseline;
        long progress = adjusted % _forceTarget;
        return MathF.Min(1f, (float)progress / _forceTarget) * 100f;
    }

    string GetMedalEmoji(int tier) => tier switch {
        3 => "🥇",
        2 => "🥈",
        1 => "🥉",
        _ => ""
    };
    
    
    /////////////////////////////////////////////////////////////////////////
    // ----------------- COMMUNITY TAB INFORMATIONS ---------------------- //
    /////////////////////////////////////////////////////////////////////////
    class CommunityData
    {
        public string Welcome  { get; set; } = "";
        public string Discord  { get; set; } = "";
        public string Devnote  { get; set; } = "";
        public List<CommunityEvent> Events { get; set; } = new();
    }
    class CommunityEvent
    {
        public string Badge { get; set; } = "";
        public string Text  { get; set; } = "";
        public string Color { get; set; } = "gray";
    }

    CommunityData _community      = null;
    bool          _communityLoading = false;
    bool          _communityError   = false;

    @* async System.Threading.Tasks.Task LoadCommunity()
    {
        if (_communityLoading) return;
        _communityLoading = true;
        _communityError   = false;
        StateHasChanged();

        try
        {
            string url  = "https://gist.githubusercontent.com/YOURUSER/GISTID/raw/community.json";
            string json = await Http.RequestStringAsync(url);
            _community  = Json.Deserialize<CommunityData>(json);
        }
        catch (Exception ex)
        {
            Log.Warning($"Community fetch failed: {ex.Message}");
            _communityError = true;
        }

        _communityLoading = false;
        StateHasChanged();
    } *@
    bool _discordCopied = false;

    async void OnDiscordCopy()
    {
        string discordUrl = "https://discord.gg/tGvWgHUsYu";
        Clipboard.SetText(discordUrl);
        _discordCopied = true;
        Sound.Play("UI_Button");
        Sound.Play("Notification_1");
        StateHasChanged();

        await GameTask.DelaySeconds(2.5f);
        _discordCopied = false;
        StateHasChanged();
    }

    bool _musicLinkCopied = false;

    async void OnMusicLinkCopy()
    {
        Clipboard.SetText("https://yourcomposer.com/profile");
        _musicLinkCopied = true;
        Sound.Play("UI_Button");
        Sound.Play("Notification_1");
        StateHasChanged();

        await GameTask.DelaySeconds(2.5f);
        _musicLinkCopied = false;
        StateHasChanged();
    }


    // ----------------------- //
    // Main menu fake bomb vfx //
    // ----------------------- //

    // Explosion state
    List<(float x, float y, float life, string emoji)> _menuExplosions = new();

    void TriggerMenuExplosion()
    {
        Sound.Play("climax_explosion_lv1_lowpass");
        
        // Spawn 5-8 particles around the title area
        int count = 5 + _rng.Next(4);
        string[] emojis = { "💥", "✨", "💣", "⚡", "🔥" };
        
        for (int i = 0; i < count; i++)
        {
            _menuExplosions.Add((
                x:     15f + (float)_rng.NextDouble() * 25f,  // left side where title is
                y:     20f + (float)_rng.NextDouble() * 40f,
                life:  1.0f,
                emoji: emojis[_rng.Next(emojis.Length)]
            ));
        }
    }

    // ============================== SURVIVAL MODE ============================== //

    void OnPlaySurvival()
    {
        if (_save?.TutorialCompleted != true) return;
        _survivalSelectedBombs.Clear();
        _survivalSelectedBombs.Add(GridManager.BombType.Cross); // Cross always available by default
        _showSurvivalLoadout = true;
        StateHasChanged();
    }

    void ToggleSurvivalBomb(GridManager.BombType type)
    {
        if (_survivalSelectedBombs.Contains(type))
        {
            if (_survivalSelectedBombs.Count > 1) // must keep at least 1
            {
                _survivalSelectedBombs.Remove(type);
                Sound.Play("UI_Button");
            }
               
        }
        else
        {
            if (_survivalSelectedBombs.Count < 3) // cap at 3
            {
                _survivalSelectedBombs.Add(type);
                Sound.Play("BuyShop");
            }
               
        }
        StateHasChanged();
    }

    bool ShowsClashWarning() =>
        _survivalSelectedBombs.Contains(GridManager.BombType.Diagonal) &&
        _survivalSelectedBombs.Contains(GridManager.BombType.Cross) &&
        _survivalSelectedBombs.Count > 1;

    void ConfirmSurvivalLoadoutAndStart()
    {
        if (_survivalSelectedBombs.Count == 0) return;
        _showSurvivalLoadout = false;
        Game?.StartGame(GameMode.Survival, _survivalSelectedBombs.ToList());
        Sound.Play("PlayButton");
        StateHasChanged();
    }
}