Game/Menu/MainMenu.razor
@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 > 0)
            {
                <div class="mm-stats-personal mm-hof-card">
                    @for(int i = 0; i < Math.Min(3, _board.Entries.Length); i++)
                    {
                        var entry = _board.Entries[i];
                        string medal = i==0?"🥇":i==1?"🥈":"🥉";
                        <div class="mm-sp-row">
                            <span class="mm-sp-icon">@medal</span>
                            <span class="mm-sp-val">@GetName(entry)</span>
                            <span class="mm-sp-lbl">@entry.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 == "reactor" ? "on" : "")"
                                        onclick=@(() => { _lbMode = "reactor"; _ = LoadReactorLb(); StateHasChanged(); })>
                                    ⚛️ Reactor
                                </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 @(IsMe(e2)?"me":"")">
                                            <span class="mm-lb-rank">@medal</span>
                                            <div class="mm-lb-name-col">
                                                <span class="mm-lb-name">@GetName(e2)</span>
                                            </div>
                                            <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 @(IsMe(e2)?"me":"")">
                                            <span class="mm-lb-rank">@medal</span>
                                            <div class="mm-lb-name-col">
                                                <span class="mm-lb-name">@GetName(e2)</span>
                                            </div>
                                            <span class="mm-lb-pet-badge">
                                                @if(lvl == 1)
                                                {
                                                    <image src="ui/icons/egg/egg_1.png" class="mm-lb-pet-badge-img"/>
                                                }
                                                else
                                                {
                                                    @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 @(IsMe(entry)?"me":"")">
                                        <span class="mm-lb-rank">@medal</span>
                                        <div class="mm-lb-name-col">
                                            <span class="mm-lb-name">@GetName(entry)</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>
                                    }
                                }
                            }

                            @* Leaderboard - REACTOR (total Flux earned) *@
                            else if(_lbMode == "reactor")
                            {
                                @if(_loadingLb)
                                {
                                    <div class="mm-panel-empty">Loading...</div>
                                }
                                else if(_reactorBoard?.Entries == null || _reactorBoard.Entries.Length == 0)
                                {
                                    <div class="mm-panel-empty">No reactors online yet — be the first!</div>
                                }
                                else
                                {
                                    @for(int i = 0; i < _reactorBoard.Entries.Length; i++)
                                    {
                                        var entry = _reactorBoard.Entries[i];
                                        int rk = i + 1;
                                        string medal = rk==1?"🥇":rk==2?"🥈":rk==3?"🥉":$"#{rk}";
                                        long flux = (long)entry.Value;
                                        <div class="mm-lb-row @(IsMe(entry)?"me":"")">
                                            <span class="mm-lb-rank">@medal</span>
                                            <div class="mm-lb-name-col">
                                                <span class="mm-lb-name">@GetName(entry)</span>
                                                <span class="mm-lb-chain-title">@GetReactorTitle(flux)</span>
                                            </div>
                                            <span class="mm-lb-chain-badge">🔷 @flux.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 = IsMe(entry) ? GetMedalEmoji(_save?.ForceMedalTier ?? 0) : "";
                                        float pct = _forceTotalScore > 0 ? (float)score / _forceTotalScore * 100f : 0f;

                                        <div class="mm-lb-row @(IsMe(entry)?"me":"")">
                                            <span class="mm-lb-rank">@medal</span>
                                            <div class="mm-lb-name-col">
                                                <span class="mm-lb-name">
                                                   @(myMedal != "" ? myMedal + " " : "")@GetName(entry)
                                                </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 : THE REACTOR ===================== *@
        @* ============================================================ *@
        else if( _tab == "reactor" )
        {
            <div class="mm-panel">
                <div class="mm-content-card mm-center-enter">
                    <div class="mm-panel-hd">⚛️ The Reactor</div>

                    @if(!(_save?.TutorialCompleted ?? false))
                    {
                        <div class="mm-reactor-locked">
                            <div class="mm-reactor-locked-icon">🔒</div>
                            <div class="mm-reactor-locked-title">Sealed</div>
                            <div class="mm-reactor-locked-desc">
                                Finish one run in Wave mode and the Reactor comes online.
                            </div>
                        </div>
                    }
                    else
                    {
                        var idl        = _save.Idle;
                        double pending = IdleReactor.PreviewOfflineCores(idl);
                        double away    = IdleReactor.PreviewOfflineSeconds(idl);
                        float  tank    = IdleReactor.PreviewStorageFraction(idl);
                        bool   daily   = IdleReactor.DailyAvailable(idl);
                        float  capH    = IdleBalance.OfflineCapHours(
                                            idl.Level(IdleUpgradeId.OfflineCap),
                                            idl.PerkLevel(IdlePerkId.DeepStorage));
                        int fragCap    = IdleBalance.DailyFragmentCap(
                                            idl.PerkLevel(IdlePerkId.FragmentCondenser), _save.PetLevel);
                        double petPct  = (IdleBalance.PetBonus(_save.PetLevel, _save.PetUnlockShown,
                                            idl.PerkLevel(IdlePerkId.PetLink)) - 1.0) * 100.0;

                        <div class="mm-reactor-blurb">
                            A board that plays itself. Chests drop, drones detonate them, chain
                            reactions print Cores — while you watch, while you play another
                            mode, and while the game is closed.
                        </div>

                        <div class="mm-reactor-tank">
                            <div class="mm-reactor-tank-top">
                                <span class="mm-reactor-tank-lbl">🛢️ OFFLINE STORAGE</span>
                                <span class="mm-reactor-tank-val">@IdleFormat.Duration(away) / @capH.ToString("F0")h</span>
                            </div>
                            <div class="mm-reactor-tank-track">
                                <div class="mm-reactor-tank-fill @(tank >= 1f ? "full" : "")"
                                     style="width:@((tank * 100f).ToString("F1"))%;"></div>
                            </div>
                            @if(pending > 0)
                            {
                                <div class="mm-reactor-pending">
                                    ⚛️ @IdleFormat.Short(pending) Cores waiting to be collected
                                </div>
                            }
                            @if(pending <= 0)
                            {
                                <div class="mm-reactor-pending dim">
                                    Nothing banked yet — run the reactor once to start production.
                                </div>
                            }
                            @if(tank >= 1f)
                            {
                                <div class="mm-reactor-warn">⚠️ Tanks are full — production is being wasted.</div>
                            }
                        </div>

                        @if(daily)
                        {
                            <div class="mm-reactor-daily">📅 Daily bonus ready — Day @(Math.Min(7, idl.DailyStreak + 1))</div>
                        }

                        <div class="mm-reactor-stats">
                            <div class="mm-reactor-stat">
                                <span class="mm-reactor-stat-val">@IdleFormat.Short(idl.Cores)</span>
                                <span class="mm-reactor-stat-lbl">⚛️ Cores</span>
                            </div>
                            <div class="mm-reactor-stat">
                                <span class="mm-reactor-stat-val">@IdleFormat.Short(idl.MeasuredCps)</span>
                                <span class="mm-reactor-stat-lbl">📈 per sec</span>
                            </div>
                            <div class="mm-reactor-stat">
                                <span class="mm-reactor-stat-val">@idl.Flux</span>
                                <span class="mm-reactor-stat-lbl">🔷 Flux</span>
                            </div>
                            <div class="mm-reactor-stat">
                                <span class="mm-reactor-stat-val">@idl.Meltdowns</span>
                                <span class="mm-reactor-stat-lbl">☢️ Meltdowns</span>
                            </div>
                            <div class="mm-reactor-stat">
                                <span class="mm-reactor-stat-val">@idl.BestChain</span>
                                <span class="mm-reactor-stat-lbl">⛓️ Best chain</span>
                            </div>
                            <div class="mm-reactor-stat">
                                <span class="mm-reactor-stat-val">@IdleAchievements.CompletedCount(idl) / @IdleAchievements.All.Count</span>
                                <span class="mm-reactor-stat-lbl">📜 Reactor Log</span>
                            </div>
                        </div>

                        <div class="mm-reactor-link">
                            <div class="mm-reactor-link-row">
                                <span class="mm-reactor-link-icon">💠</span>
                                <span class="mm-reactor-link-txt">
                                    Cores condense into Fragments for the shop —
                                    @idl.FragmentsToday / @fragCap today
                                </span>
                            </div>
                            <div class="mm-reactor-link-row">
                                <span class="mm-reactor-link-icon">🐾</span>
                                <span class="mm-reactor-link-txt">
                                    Your pet boosts reactor output by +@petPct.ToString("F0")%
                                    — level it up in Wave mode
                                </span>
                            </div>
                        </div>

                        <button class="mm-reactor-enter" style="pointer-events:all;"
                                onclick=@(() => { OnPlayReactor(); Sound.Play("PlayButton"); })>
                            ⚛️ ENTER THE REACTOR ▶
                        </button>
                    }
                </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>
            @* =================== REACTOR (IDLE) MODE  =================== *@
            @{
                bool reactorUnlocked = _save?.TutorialCompleted ?? false;
                var  idleSave        = _save?.Idle;
                bool reactorAlert    = reactorUnlocked && idleSave != null
                                        && (IdleReactor.PreviewOfflineCores(idleSave) > 0
                                            || IdleReactor.DailyAvailable(idleSave));
            }
            <div class="mm-reactor-wrap">
                <button class="mm-reactor @(reactorUnlocked && MainMenuTutorial.Instance?.IsActive != true ? "" : "mm-mode-locked")"
                        onclick=@(() => {
                            if(reactorUnlocked && MainMenuTutorial.Instance?.IsActive != true)
                            { OnPlayReactor(); Sound.Play("PlayButton"); } })>
                    @if(reactorUnlocked)
                    {
                        <span>⚛️</span>
                        <span class="mm-reactor-label">THE REACTOR</span>
                        <span class="mm-reactor-sub">idle · always running</span>
                        @if(reactorAlert)
                        {
                            <span class="mm-reactor-dot"></span>
                        }
                    }
                    else
                    {
                        <span>🔒</span>
                        <span class="mm-reactor-label">Complete a run to unlock</span>
                    }
                </button>
            </div>
            @* =================== END =================== *@

            @* =================== 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>

                    @* ── Reactor tab ── *@
                    @{
                        var idleTabSave = _save?.Idle;
                        bool idleTabAlert = (_save?.TutorialCompleted ?? false) && idleTabSave != null
                            && (IdleReactor.PreviewOfflineCores(idleTabSave) > 0
                                || IdleReactor.DailyAvailable(idleTabSave));
                    }
                    <button class="mm-tab @(_tab == "reactor" ? "on" : "") @(MainMenuTutorial.Instance?.IsActive == true ? "mm-tab-tut-locked" : "")"
                            onclick=@(() => {
                                if(MainMenuTutorial.Instance?.IsActive != true) SetTab("reactor");
                                Sound.Play("UI_Button"); })>
                        ⚛️ Reactor
                        @if(idleTabAlert)
                        {
                            <span class="mm-frags-pill">!</span>
                        }
                    </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;
    Sandbox.Services.Leaderboards.Board2 _board;
    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.Board2 _chainBoard = null;

    Leaderboards.Board2 _petBoard = null;
    Leaderboards.Board2 _reactorBoard = null;
    const string GameIdent = "playnplay.10_xplosion";

    // 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);

    // Reactor (Idle) mode — no loadout screen, it is always mid-run
    void OnPlayReactor()
    {
        if (_save?.TutorialCompleted != true) return;
        Game?.StartGame(GameMode.Idle, 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 == "reactor")_ = LoadReactorLb();
            else if(_lbMode == "force")  _ = LoadForceLb();
        }

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

   async System.Threading.Tasks.Task LoadLb()
{
    try
    {
        string statName = _lbMode switch
        {
            "score" => "chain_reaction_score",
            "pet"   => "chain_reaction_pet",
            "chain" => "chain_reaction_chain",
            _       => "chain_reaction_score"
        };

        var board = Sandbox.Services.Leaderboards.GetFromStat(GameIdent, statName);
        board.SetAggregationMax();   // keep each player's BEST, not the sum
        board.SetSortDescending();   // highest first
        board.MaxEntries = 100;

        await board.Refresh();

        Log.Info($"LB '{statName}' — entries: {board.Entries?.Length ?? -1}");
        _board = board;
    }
    catch (Exception ex)
    {
        Log.Warning($"LB error: {ex.Message}");
        _board = null;
    }
    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 = Sandbox.Services.Leaderboards.GetFromStat(GameIdent, "chain_reaction_pet");
            b.SetAggregationMax();
            b.SetSortDescending();
            b.MaxEntries = 100;
            await b.Refresh();
            _petBoard = b;
        }
        catch(Exception ex) { Log.Warning($"Pet LB: {ex.Message}"); }
        _loadingLb = false;
        StateHasChanged();
    }

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

    async System.Threading.Tasks.Task LoadReactorLb()
    {
        _loadingLb = true;
        _reactorBoard = null;
        StateHasChanged();

        try
        {
            var b = Sandbox.Services.Leaderboards.GetFromStat(GameIdent, "chain_reaction_reactor");
            b.SetAggregationMax();
            b.SetSortDescending();
            b.MaxEntries = 100;
            await b.Refresh();
            _reactorBoard = b;
        }
        catch(Exception ex) { Log.Warning($"Reactor LB: {ex.Message}"); }
        _loadingLb = false;
        StateHasChanged();
    }

    static string GetReactorTitle(long flux) => flux switch {
        >= 5000 => "☢️ Singularity",
        >= 2000 => "🌟 Core Architect",
        >= 750  => "⚛️ Reactor Chief",
        >= 250  => "🔷 Fluxbound",
        >= 60   => "⚙️ Engineer",
        >= 10   => "🔧 Technician",
        _       => "🧪 Intern",
    };

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

        try
        {
            var b = Sandbox.Services.Leaderboards.GetFromStat(GameIdent, "chain_reaction_chain");
            b.SetAggregationMax();
            b.SetSortDescending();
            b.MaxEntries = 100;
            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.Board2 _forceBoard;
    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 = Sandbox.Services.Leaderboards.GetFromStat(GameIdent, "chain_reaction_score");
            if(b != null)
            {
                b.SetAggregationMax();
                b.SetSortDescending();
                b.MaxEntries = 100;
                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 => IsMe(e));
                    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.Board2 b)
    {
        for(int i = 0; i < b.Entries.Length; i++)
        {
            if(!IsMe(b.Entries[i])) continue;
            if(i < 3)  return 3;
            if(i < 10) return 2;
            return 1;
        }
        return 0;
    }

    bool IsMe(Leaderboards.Board2.Entry entry)
    {
        var localId = Sandbox.Connection.Local?.SteamId;
        return entry.SteamId == localId;
    }

    bool IsMe(Leaderboards.Entry entry)
    {
        var localId = Sandbox.Connection.Local?.SteamId;
        return entry.SteamId == localId;
    }

    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();
    }

    string GetName(Leaderboards.Board2.Entry entry)
    {
        if(!string.IsNullOrWhiteSpace(entry.DisplayName)) return entry.DisplayName;
        try
        {
            var friend = new Sandbox.Friend(entry.SteamId);
            if(!string.IsNullOrWhiteSpace(friend.Name)) return friend.Name;
        }
        catch { }
        return $"Player {entry.SteamId}";
    }

    string GetName(Leaderboards.Entry entry)
    {
        if(!string.IsNullOrWhiteSpace(entry.DisplayName)) return entry.DisplayName;
        try
        {
            var friend = new Sandbox.Friend(entry.SteamId);
            if(!string.IsNullOrWhiteSpace(friend.Name)) return friend.Name;
        }
        catch { }
        return $"Player {entry.SteamId}";
    }
}