#include "MenuState.h"
#include "persistence/Scores.h"
#include "../network/supabase_client.h"
#include "../network/NetSession.h"
#include "graphics/Font.h"
#include "../graphics/ui/HelpOverlay.h"
#include "../core/GlobalState.h"
#include "../core/Settings.h"
#include "../core/state/StateManager.h"
#include "../audio/Audio.h"
#include "../audio/AudioManager.h"
#include "../audio/SoundEffect.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_surface.h>
#include <cstdio>
#include <algorithm>
#include <array>
#include <cmath>
#include <vector>
#include <random>

// Use dynamic logical dimensions from GlobalState instead of hardcoded values
// This allows the UI to adapt when the window is resized or goes fullscreen

// Shared flags and resources are provided via StateContext `ctx`.
// Removed fragile extern declarations and use `ctx.showLevelPopup`, `ctx.showSettingsPopup`,
// `ctx.musicEnabled` and `ctx.hoveredButton` instead to avoid globals.
// Menu helper wrappers are declared in a shared header implemented in main.cpp
#include "../audio/MenuWrappers.h"
#include "../utils/ImagePathResolver.h"
#include "../graphics/renderers/UIRenderer.h"
#include "../graphics/renderers/GameRenderer.h"
#include "../ui/MenuLayout.h"
#include "../ui/BottomMenu.h"
#include <SDL3_image/SDL_image.h>

// Frosted tint helper: draw a safe, inexpensive frosted overlay for the panel area.
// This avoids renderer readback / surface APIs which aren't portable across SDL3 builds.
static void renderBackdropBlur(SDL_Renderer* renderer, const SDL_Rect& logicalVP, float logicalScale, float panelTop, float panelH, SDL_Texture* sceneTex, int sceneW, int sceneH) {
    if (!renderer) return;
    // Preserve previous draw blend mode so callers don't get surprised when
    // the helper early-returns or changes blend state.
    SDL_BlendMode prevBlendMode = SDL_BLENDMODE_NONE;
    SDL_GetRenderDrawBlendMode(renderer, &prevBlendMode);
    // If we don't have a captured scene texture, fall back to the frosted tint.
    if (!sceneTex || sceneW <= 0 || sceneH <= 0) {
        float viewportLogicalW = (float)logicalVP.w / logicalScale;
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(renderer, 200, 210, 220, 48);
        SDL_FRect base{ 0.0f, panelTop, viewportLogicalW, panelH };
        SDL_RenderFillRect(renderer, &base);
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 24);
        SDL_FRect highlight{ 0.0f, panelTop, viewportLogicalW, std::max(2.0f, panelH * 0.06f) };
        SDL_RenderFillRect(renderer, &highlight);
        SDL_SetRenderDrawColor(renderer, 16, 24, 32, 12);
        SDL_FRect shadow{ 0.0f, panelTop + panelH - std::max(2.0f, panelH * 0.06f), viewportLogicalW, std::max(2.0f, panelH * 0.06f) };
        SDL_RenderFillRect(renderer, &shadow);
        // Restore previous blend mode
        SDL_SetRenderDrawBlendMode(renderer, prevBlendMode);
        return;
    }

    // Compute source rect in scene texture pixels for the panel area
    int panelWinX = 0;
    int panelWinY = static_cast<int>(std::floor(panelTop * logicalScale + logicalVP.y));
    int panelWinW = sceneW; // full width of captured scene
    int panelWinH = static_cast<int>(std::ceil(panelH * logicalScale));
    if (panelWinW <= 0 || panelWinH <= 0) return;

    // Downsample size (cheap Gaussian-ish blur via scale).
    int blurW = std::max(8, panelWinW / 6);
    int blurH = std::max(4, panelWinH / 6);

    // Create a small render target to draw the downsampled region into
    SDL_Texture* small = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, blurW, blurH);
    if (!small) {
        // Fall back to tint if we can't allocate
        float viewportLogicalW = (float)logicalVP.w / logicalScale;
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(renderer, 200, 210, 220, 48);
        SDL_FRect base{ 0.0f, panelTop, viewportLogicalW, panelH };
        SDL_RenderFillRect(renderer, &base);
        SDL_SetRenderDrawBlendMode(renderer, prevBlendMode);
        return;
    }

    // Source rectangle in the scene texture (pixel coords) as floats
    SDL_FRect srcRectF{ (float)panelWinX, (float)panelWinY, (float)panelWinW, (float)panelWinH };

    // Render the sub-region of the scene into the small texture
    SDL_SetRenderTarget(renderer, small);
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
    SDL_RenderClear(renderer);

    SDL_FRect smallDst{ 0.0f, 0.0f, (float)blurW, (float)blurH };
    SDL_RenderTexture(renderer, sceneTex, &srcRectF, &smallDst);

    // Reset target
    SDL_SetRenderTarget(renderer, nullptr);

    // Render the small texture scaled up to the panel area with linear filtering
    SDL_SetTextureBlendMode(small, SDL_BLENDMODE_BLEND);
    SDL_SetTextureScaleMode(small, SDL_SCALEMODE_LINEAR);

    float viewportLogicalW = (float)logicalVP.w / logicalScale;
    SDL_FRect dst{ 0.0f, panelTop, viewportLogicalW, panelH };
    SDL_RenderTexture(renderer, small, nullptr, &dst);

    // Cleanup
    SDL_DestroyTexture(small);
    // Restore previous blend mode so caller drawing is unaffected
    SDL_SetRenderDrawBlendMode(renderer, prevBlendMode);
}

MenuState::MenuState(StateContext& ctx) : State(ctx) {}

void MenuState::showCoopSetupPanel(bool show, bool resumeMusic) {
    if (show) {
        if (!coopSetupVisible && !coopSetupAnimating) {
            // Avoid overlapping panels
            if (aboutPanelVisible && !aboutPanelAnimating) {
                aboutPanelAnimating = true;
                aboutDirection = -1;
            }
            if (helpPanelVisible && !helpPanelAnimating) {
                helpPanelAnimating = true;
                helpDirection = -1;
            }
            if (optionsVisible && !optionsAnimating) {
                optionsAnimating = true;
                optionsDirection = -1;
            }
            if (levelPanelVisible && !levelPanelAnimating) {
                levelPanelAnimating = true;
                levelDirection = -1;
            }
            if (exitPanelVisible && !exitPanelAnimating) {
                exitPanelAnimating = true;
                exitDirection = -1;
                if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false;
            }

            coopSetupAnimating = true;
            coopSetupDirection = 1;
            coopSetupSelected = (ctx.coopVsAI && *ctx.coopVsAI) ? 1 : 0;
            coopSetupStep = CoopSetupStep::ChoosePartner;
            coopNetworkRoleSelected = 0;
            coopNetworkHandshakeSent = false;
            coopNetworkStatusText.clear();
            if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
                SDL_StopTextInput(focusWin);
            }
            if (coopNetworkSession) {
                coopNetworkSession->shutdown();
                coopNetworkSession.reset();
            }
            coopSetupRectsValid = false;
            selectedButton = static_cast<int>(ui::BottomMenuItem::Cooperate);
            // Ensure the transition value is non-zero so render code can show
            // the inline choice buttons immediately on the same frame.
            if (coopSetupTransition <= 0.0) coopSetupTransition = 0.001;
        }
    } else {
        if (coopSetupVisible && !coopSetupAnimating) {
            coopSetupAnimating = true;
            coopSetupDirection = -1;
            coopSetupRectsValid = false;

            if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
                SDL_StopTextInput(focusWin);
            }

            // Cancel any pending network session if the coop setup is being closed.
            if (coopNetworkSession) {
                coopNetworkSession->shutdown();
                coopNetworkSession.reset();
            }
            coopNetworkHandshakeSent = false;
            coopNetworkStatusText.clear();
            coopSetupStep = CoopSetupStep::ChoosePartner;
            // Resume menu music only when requested (ESC should pass resumeMusic=false)
            if (resumeMusic && ctx.musicEnabled && *ctx.musicEnabled) {
                if (auto sys = AudioManager::get()) sys->playMenuMusic();
            }
        }
    }
}

void MenuState::showHelpPanel(bool show) {
    if (show) {
        if (!helpPanelVisible && !helpPanelAnimating) {
            // Avoid overlapping panels
            if (aboutPanelVisible && !aboutPanelAnimating) {
                aboutPanelAnimating = true;
                aboutDirection = -1;
            }
            helpPanelAnimating = true;
            helpDirection = 1;
            helpScroll = 0.0;
        }
    } else {
        if (helpPanelVisible && !helpPanelAnimating) {
            helpPanelAnimating = true;
            helpDirection = -1;
        }
    }
}

void MenuState::showAboutPanel(bool show) {
    if (show) {
        if (!aboutPanelVisible && !aboutPanelAnimating) {
            // Avoid overlapping panels
            if (helpPanelVisible && !helpPanelAnimating) {
                helpPanelAnimating = true;
                helpDirection = -1;
            }
            if (optionsVisible && !optionsAnimating) {
                optionsAnimating = true;
                optionsDirection = -1;
            }
            if (levelPanelVisible && !levelPanelAnimating) {
                levelPanelAnimating = true;
                levelDirection = -1;
            }
            if (exitPanelVisible && !exitPanelAnimating) {
                exitPanelAnimating = true;
                exitDirection = -1;
                if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false;
            }
            aboutPanelAnimating = true;
            aboutDirection = 1;
        }
    } else {
        if (aboutPanelVisible && !aboutPanelAnimating) {
            aboutPanelAnimating = true;
            aboutDirection = -1;
        }
    }
}

void MenuState::onEnter() {
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState::onEnter called");
    if (ctx.showExitConfirmPopup) {
        *ctx.showExitConfirmPopup = false;
    }
    if (ctx.exitPopupSelectedButton) {
        *ctx.exitPopupSelectedButton = 1;
    }
    // Refresh highscores for classic/cooperate/challenge asynchronously
    try {
        std::thread([this]() {
            try {
                auto c_classic = supabase::FetchHighscores("classic", 10);
                auto c_coop = supabase::FetchHighscores("cooperate", 10);
                auto c_challenge = supabase::FetchHighscores("challenge", 10);
                std::vector<ScoreEntry> combined;
                combined.reserve(c_classic.size() + c_coop.size() + c_challenge.size());
                combined.insert(combined.end(), c_classic.begin(), c_classic.end());
                combined.insert(combined.end(), c_coop.begin(), c_coop.end());
                combined.insert(combined.end(), c_challenge.begin(), c_challenge.end());
                if (this->ctx.scores) this->ctx.scores->replaceAll(combined);
            } catch (...) {
                // swallow network errors - keep existing scores
            }
        }).detach();
    } catch (...) {}
}

void MenuState::renderMainButtonTop(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
    const float LOGICAL_W = 1200.f;
    const float LOGICAL_H = 1000.f;

    // Use the same layout code as mouse hit-testing so each button is the same size.
    ui::MenuLayoutParams params{
        static_cast<int>(LOGICAL_W),
        static_cast<int>(LOGICAL_H),
        logicalVP.w,
        logicalVP.h,
        logicalScale
    };

    int startLevel = ctx.startLevelSelection ? *ctx.startLevelSelection : 0;
    const bool coopVsAI = ctx.coopVsAI ? *ctx.coopVsAI : false;
    ui::BottomMenu menu = ui::buildBottomMenu(params, startLevel, coopVsAI);

    const int hovered = (ctx.hoveredButton ? *ctx.hoveredButton : -1);
        const double baseAlpha = 1.0; // Base alpha for button rendering
    // Pulse is encoded as a signed delta so PLAY can dim/brighten while focused.
    const double pulseDelta = (buttonPulseAlpha - 1.0);
    const double flashDelta = buttonFlash * buttonFlashAmount;
    ui::renderBottomMenu(renderer, ctx.pixelFont, menu, hovered, selectedButton, baseAlpha, pulseDelta + flashDelta);
}

void MenuState::onExit() {
    if (ctx.showExitConfirmPopup) {
        *ctx.showExitConfirmPopup = false;
    }
    
    // Clean up icon textures
    if (playIcon) { SDL_DestroyTexture(playIcon); playIcon = nullptr; }
    if (levelIcon) { SDL_DestroyTexture(levelIcon); levelIcon = nullptr; }
    if (optionsIcon) { SDL_DestroyTexture(optionsIcon); optionsIcon = nullptr; }
    if (exitIcon) { SDL_DestroyTexture(exitIcon); exitIcon = nullptr; }
    if (helpIcon) { SDL_DestroyTexture(helpIcon); helpIcon = nullptr; }
    if (coopInfoTexture) { SDL_DestroyTexture(coopInfoTexture); coopInfoTexture = nullptr; }
}

void MenuState::handleEvent(const SDL_Event& e) {
    // Text input for network IP entry (only while coop setup panel is active).
    if ((coopSetupVisible || coopSetupAnimating) && coopSetupTransition > 0.0 && e.type == SDL_EVENT_TEXT_INPUT) {
        if (coopSetupStep == CoopSetupStep::NetworkEnterAddress) {
            std::string& target = (coopNetworkRoleSelected == 0) ? coopNetworkBindAddress : coopNetworkJoinAddress;
            if (target.size() < 64) {
                target += e.text.text;
            }
            return;
        }
    }

    // Coop setup panel navigation (modal within the menu)
    // Handle this FIRST and consume key events so the main menu navigation doesn't interfere.
    // Note: Do not require !repeat here; some keyboards/OS configs may emit Enter with repeat.
    if ((coopSetupVisible || coopSetupAnimating) && coopSetupTransition > 0.0 && e.type == SDL_EVENT_KEY_DOWN) {
        // Coop setup panel navigation (modal within the menu)
        switch (e.key.scancode) {
            case SDL_SCANCODE_UP:
            case SDL_SCANCODE_DOWN:
                // Do NOT allow up/down to change anything while this panel is active
                return;
            case SDL_SCANCODE_ESCAPE:
                // When in a nested network step, go back one step; otherwise close the panel.
                if (coopSetupStep == CoopSetupStep::NetworkChooseRole) {
                    coopSetupStep = CoopSetupStep::ChoosePartner;
                    coopNetworkHandshakeSent = false;
                    coopNetworkStatusText.clear();
                    return;
                }
                if (coopSetupStep == CoopSetupStep::NetworkEnterAddress) {
                    if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
                        SDL_StopTextInput(focusWin);
                    }
                    coopSetupStep = CoopSetupStep::NetworkChooseRole;
                    coopNetworkHandshakeSent = false;
                    coopNetworkStatusText.clear();
                    return;
                }
                if (coopSetupStep == CoopSetupStep::NetworkWaiting) {
                    if (coopNetworkSession) {
                        coopNetworkSession->shutdown();
                        coopNetworkSession.reset();
                    }
                    if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
                        SDL_StopTextInput(focusWin);
                    }
                    coopNetworkHandshakeSent = false;
                    coopNetworkStatusText.clear();
                    coopSetupStep = CoopSetupStep::NetworkChooseRole;
                    return;
                }
                showCoopSetupPanel(false, false);
                return;
            case SDL_SCANCODE_LEFT:
            case SDL_SCANCODE_A:
                if (coopSetupStep == CoopSetupStep::ChoosePartner) {
                    // 3-way selection: LOCAL / AI / NETWORK
                    coopSetupSelected = (coopSetupSelected + 3 - 1) % 3;
                    buttonFlash = 1.0;
                    return;
                }
                if (coopSetupStep == CoopSetupStep::NetworkChooseRole) {
                    coopNetworkRoleSelected = (coopNetworkRoleSelected + 2 - 1) % 2;
                    buttonFlash = 1.0;
                    return;
                }
                if (coopSetupStep == CoopSetupStep::NetworkEnterAddress) {
                    return;
                }
                return;
            case SDL_SCANCODE_RIGHT:
            case SDL_SCANCODE_D:
                if (coopSetupStep == CoopSetupStep::ChoosePartner) {
                    coopSetupSelected = (coopSetupSelected + 1) % 3;
                    buttonFlash = 1.0;
                    return;
                }
                if (coopSetupStep == CoopSetupStep::NetworkChooseRole) {
                    coopNetworkRoleSelected = (coopNetworkRoleSelected + 1) % 2;
                    buttonFlash = 1.0;
                    return;
                }
                if (coopSetupStep == CoopSetupStep::NetworkEnterAddress) {
                    return;
                }
                return;
            case SDL_SCANCODE_BACKSPACE:
                if (coopSetupStep == CoopSetupStep::NetworkEnterAddress) {
                    std::string& target = (coopNetworkRoleSelected == 0) ? coopNetworkBindAddress : coopNetworkJoinAddress;
                    if (!target.empty()) target.pop_back();
                    return;
                }
                break;
            case SDL_SCANCODE_RETURN:
            case SDL_SCANCODE_KP_ENTER:
            case SDL_SCANCODE_SPACE:
            {
                // Existing flows (Local 2P / AI) are preserved exactly.
                if (coopSetupStep == CoopSetupStep::ChoosePartner && (coopSetupSelected == 0 || coopSetupSelected == 1)) {
                    const bool useAI = (coopSetupSelected == 1);
                    if (ctx.coopVsAI) {
                        *ctx.coopVsAI = useAI;
                    }
                    if (ctx.game) {
                        ctx.game->setMode(GameMode::Cooperate);
                        ctx.game->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
                    }
                    if (ctx.coopGame) {
                        ctx.coopGame->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
                    }

                    // Close the panel without restarting menu music; gameplay will take over.
                    showCoopSetupPanel(false, false);

                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
                                "MenuState: coop start via key, selected=%d, startPlayTransition_present=%d, stateManager=%p",
                                coopSetupSelected,
                                ctx.startPlayTransition ? 1 : 0,
                                (void*)ctx.stateManager);

                    if (ctx.startPlayTransition) {
                        ctx.startPlayTransition();
                    } else if (ctx.stateManager) {
                        ctx.stateManager->setState(AppState::Playing);
                    }
                    return;
                }

                // Network flow (new): choose host/join, confirm connection before starting.
                if (coopSetupStep == CoopSetupStep::ChoosePartner && coopSetupSelected == 2) {
                    coopSetupStep = CoopSetupStep::NetworkChooseRole;
                    coopNetworkRoleSelected = 0;
                    coopNetworkHandshakeSent = false;
                    coopNetworkStatusText.clear();
                    if (coopNetworkSession) {
                        coopNetworkSession->shutdown();
                        coopNetworkSession.reset();
                    }
                    buttonFlash = 1.0;
                    return;
                }

                if (coopSetupStep == CoopSetupStep::NetworkChooseRole) {
                    // First, let the user enter the address (bind for host, remote for join).
                    coopSetupStep = CoopSetupStep::NetworkEnterAddress;
                    coopNetworkStatusText.clear();
                    if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
                        SDL_StartTextInput(focusWin);
                    }
                    return;
                }

                if (coopSetupStep == CoopSetupStep::NetworkEnterAddress) {
                    coopNetworkHandshakeSent = false;
                    coopNetworkStatusText.clear();
                    coopNetworkSession = std::make_unique<NetSession>();

                    const uint16_t port = coopNetworkPort;
                    bool ok = false;
                    if (coopNetworkRoleSelected == 0) {
                        const std::string bindIp = coopNetworkBindAddress;
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[NET COOP] HOST start bind=%s port=%u", bindIp.c_str(), (unsigned)port);
                        ok = coopNetworkSession->host(bindIp, port);
                        coopNetworkStatusText = ok ? "WAITING FOR PLAYER..." : ("HOST FAILED: " + coopNetworkSession->lastError());
                    } else {
                        const std::string joinIp = coopNetworkJoinAddress.empty() ? std::string("127.0.0.1") : coopNetworkJoinAddress;
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[NET COOP] JOIN start remote=%s port=%u", joinIp.c_str(), (unsigned)port);
                        ok = coopNetworkSession->join(joinIp, port);
                        coopNetworkStatusText = ok ? "CONNECTING..." : ("JOIN FAILED: " + coopNetworkSession->lastError());
                    }

                    if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
                        SDL_StopTextInput(focusWin);
                    }

                    if (ok) {
                        coopSetupStep = CoopSetupStep::NetworkWaiting;
                    } else {
                        // Stay on role choice screen so user can back out.
                        coopNetworkSession.reset();
                        coopSetupStep = CoopSetupStep::NetworkChooseRole;
                    }
                    return;
                }

                // While waiting for connection, Enter does nothing.
                return;
            }
            default:
                // Allow other keys, but don't let them affect the main menu while coop is open.
                return;
        }
    }

    // Mouse input for COOP setup panel or inline coop buttons
    if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
        if (coopSetupRectsValid) {
            // While the coop submenu is active (animating or visible) we disallow
            // mouse interaction — only keyboard LEFT/RIGHT/ESC is permitted.
            if (coopSetupAnimating || coopSetupVisible) {
                return;
            }
            const float mx = static_cast<float>(e.button.x);
            const float my = static_cast<float>(e.button.y);
            if (mx >= lastLogicalVP.x && my >= lastLogicalVP.y && mx <= (lastLogicalVP.x + lastLogicalVP.w) && my <= (lastLogicalVP.y + lastLogicalVP.h)) {
                const float lx = (mx - lastLogicalVP.x) / std::max(0.0001f, lastLogicalScale);
                const float ly = (my - lastLogicalVP.y) / std::max(0.0001f, lastLogicalScale);

                auto hit = [&](const SDL_FRect& r) {
                    return lx >= r.x && lx <= (r.x + r.w) && ly >= r.y && ly <= (r.y + r.h);
                };

                int chosen = -1;
                if (hit(coopSetupBtnRects[0])) chosen = 0;
                else if (hit(coopSetupBtnRects[1])) chosen = 1;

                if (chosen != -1) {
                    coopSetupSelected = chosen;
                    const bool useAI = (coopSetupSelected == 1);
                    if (ctx.coopVsAI) {
                        *ctx.coopVsAI = useAI;
                    }
                    if (ctx.game) {
                        ctx.game->setMode(GameMode::Cooperate);
                        ctx.game->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
                    }
                    if (ctx.coopGame) {
                        ctx.coopGame->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
                    }
                    showCoopSetupPanel(false);
                    if (ctx.startPlayTransition) {
                        ctx.startPlayTransition();
                    } else if (ctx.stateManager) {
                        ctx.stateManager->setState(AppState::Playing);
                    }
                    return;
                }
            }
        }
    }

    // Keyboard navigation for menu buttons
    if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
        // When the player uses the keyboard, don't let an old mouse hover keep focus on a button.
        if (ctx.hoveredButton) {
            *ctx.hoveredButton = -1;
        }

        auto triggerPlay = [&]() {
            if (ctx.startPlayTransition) {
                ctx.startPlayTransition();
            } else if (ctx.stateManager) {
                ctx.stateManager->setState(AppState::Playing);
            }
        };

        auto setExitSelection = [&](int value) {
            if (ctx.exitPopupSelectedButton) {
                *ctx.exitPopupSelectedButton = value;
            }
        };
        auto getExitSelection = [&]() -> int {
            return ctx.exitPopupSelectedButton ? *ctx.exitPopupSelectedButton : 1;
        };
        auto isExitPromptVisible = [&]() -> bool {
            return ctx.showExitConfirmPopup && *ctx.showExitConfirmPopup;
        };
        auto setExitPrompt = [&](bool visible) {
            if (ctx.showExitConfirmPopup) {
                *ctx.showExitConfirmPopup = visible;
            }
        };

        // Inline exit HUD handling (replaces the old modal popup)
        if (exitPanelVisible && !exitPanelAnimating) {
            switch (e.key.scancode) {
                case SDL_SCANCODE_LEFT:
                    // Move selection to YES
                    exitSelectedButton = 0;
                    if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
                    return;
                case SDL_SCANCODE_RIGHT:
                    // Move selection to NO
                    exitSelectedButton = 1;
                    if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
                    return;
                case SDL_SCANCODE_RETURN:
                case SDL_SCANCODE_KP_ENTER:
                case SDL_SCANCODE_SPACE:
                    if (exitSelectedButton == 0) {
                        // Confirm quit
                        if (ctx.requestQuit) {
                            ctx.requestQuit();
                        } else {
                            SDL_Event quit{}; quit.type = SDL_EVENT_QUIT; SDL_PushEvent(&quit);
                        }
                    } else {
                        // Close HUD
                        exitPanelAnimating = true; exitDirection = -1;
                        if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false;
                    }
                    return;
                case SDL_SCANCODE_ESCAPE:
                    showCoopSetupPanel(false, true);
                    // Cannot print std::function as a pointer; print presence (1/0) instead
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: coop ENTER pressed, selected=%d, startPlayTransition_present=%d, stateManager=%p", coopSetupSelected, ctx.startPlayTransition ? 1 : 0, (void*)ctx.stateManager);
                    if (ctx.startPlayTransition) {
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: calling startPlayTransition");
                        ctx.startPlayTransition();
                    } else {
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: startPlayTransition is null");
                    }
                    if (ctx.stateManager) {
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: setting AppState::Playing on stateManager");
                        ctx.stateManager->setState(AppState::Playing);
                    } else {
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: stateManager is null");
                    }
                    if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false;
                    return;
                case SDL_SCANCODE_PAGEDOWN:
                case SDL_SCANCODE_DOWN: {
                    // scroll content down
                    exitScroll += 40.0; return;
                }
                case SDL_SCANCODE_PAGEUP:
                case SDL_SCANCODE_UP: {
                    exitScroll -= 40.0; if (exitScroll < 0.0) exitScroll = 0.0; return;
                }
                default:
                    return;
            }
        }

        // If the inline options HUD is visible and not animating, capture navigation
        if (optionsVisible && !optionsAnimating) {
            // Options rows drawn here are 5 (Fullscreen, Music, Sound FX, Smooth Scroll, Return)
            constexpr int OPTIONS_ROW_COUNT = 5;
            switch (e.key.scancode) {
                case SDL_SCANCODE_UP:
                {
                    optionsSelectedRow = (optionsSelectedRow + OPTIONS_ROW_COUNT - 1) % OPTIONS_ROW_COUNT;
                    return;
                }
                case SDL_SCANCODE_DOWN:
                {
                    optionsSelectedRow = (optionsSelectedRow + 1) % OPTIONS_ROW_COUNT;
                    return;
                }
                case SDL_SCANCODE_LEFT:
                case SDL_SCANCODE_RIGHT:
                case SDL_SCANCODE_RETURN:
                case SDL_SCANCODE_KP_ENTER:
                case SDL_SCANCODE_SPACE:
                {
                    // Perform toggle/action for the selected option
                    switch (optionsSelectedRow) {
                        case 0: {
                            // FULLSCREEN
                            bool nextState = ! (ctx.fullscreenFlag ? *ctx.fullscreenFlag : Settings::instance().isFullscreen());
                            if (ctx.fullscreenFlag) *ctx.fullscreenFlag = nextState;
                            if (ctx.applyFullscreen) ctx.applyFullscreen(nextState);
                            Settings::instance().setFullscreen(nextState);
                            Settings::instance().save();
                            return;
                        }
                        case 1: {
                            // MUSIC
                            bool next = !Settings::instance().isMusicEnabled();
                            Settings::instance().setMusicEnabled(next);
                            Settings::instance().save();
                            if (ctx.musicEnabled) *ctx.musicEnabled = next;
                            return;
                        }
                        case 2: {
                            // SOUND FX
                            bool next = !SoundEffectManager::instance().isEnabled();
                            SoundEffectManager::instance().setEnabled(next);
                            Settings::instance().setSoundEnabled(next);
                            Settings::instance().save();
                            return;
                        }
                        case 3: {
                            // SMOOTH SCROLL
                            bool next = !Settings::instance().isSmoothScrollEnabled();
                            Settings::instance().setSmoothScrollEnabled(next);
                            Settings::instance().save();
                            return;
                        }
                        case 4: {
                            // RETURN TO MENU -> hide panel
                            optionsAnimating = true;
                            optionsDirection = -1;
                            return;
                        }
                    }
                }
                default:
                    break;
            }
        }

        // If the inline help HUD is visible and not animating, capture navigation
        if (helpPanelVisible && !helpPanelAnimating) {
            switch (e.key.scancode) {
                case SDL_SCANCODE_ESCAPE:
                case SDL_SCANCODE_RETURN:
                case SDL_SCANCODE_KP_ENTER:
                case SDL_SCANCODE_SPACE:
                    // Close help panel
                    helpPanelAnimating = true; helpDirection = -1;
                    return;
                case SDL_SCANCODE_LEFT:
                case SDL_SCANCODE_RIGHT:
                case SDL_SCANCODE_UP:
                case SDL_SCANCODE_DOWN:
                    // Arrow keys: close help and immediately return to main menu navigation.
                    helpPanelAnimating = true; helpDirection = -1;
                    break;
                case SDL_SCANCODE_PAGEDOWN:
                    helpScroll += 40.0;
                    return;
                case SDL_SCANCODE_PAGEUP:
                    helpScroll -= 40.0;
                    if (helpScroll < 0.0) helpScroll = 0.0;
                    return;
                default:
                    return;
            }
        }

        // If the inline about HUD is visible and not animating, capture navigation
        if (aboutPanelVisible && !aboutPanelAnimating) {
            switch (e.key.scancode) {
                case SDL_SCANCODE_ESCAPE:
                case SDL_SCANCODE_RETURN:
                case SDL_SCANCODE_KP_ENTER:
                case SDL_SCANCODE_SPACE:
                    aboutPanelAnimating = true; aboutDirection = -1;
                    return;
                case SDL_SCANCODE_LEFT:
                case SDL_SCANCODE_RIGHT:
                case SDL_SCANCODE_UP:
                case SDL_SCANCODE_DOWN:
                    aboutPanelAnimating = true; aboutDirection = -1;
                    break;
                default:
                    return;
            }
        }

        // If inline level HUD visible and not animating, capture navigation
        if (levelPanelVisible && !levelPanelAnimating) {
            // Start navigation from tentative hover if present, otherwise from committed selection
            int c = (levelHovered >= 0) ? levelHovered : (levelSelected < 0 ? 0 : levelSelected);
            switch (e.key.scancode) {
                case SDL_SCANCODE_LEFT:  if (c % 4 > 0) c--; break;
                case SDL_SCANCODE_RIGHT: if (c % 4 < 3) c++; break;
                case SDL_SCANCODE_UP:    if (c / 4 > 0) c -= 4; break;
                case SDL_SCANCODE_DOWN:  if (c / 4 < 4) c += 4; break;
                case SDL_SCANCODE_RETURN:
                case SDL_SCANCODE_KP_ENTER:
                case SDL_SCANCODE_SPACE:
                    // Confirm tentative selection
                    levelSelected = c;
                    if (ctx.startLevelSelection) *ctx.startLevelSelection = levelSelected;
                    // close HUD
                    levelPanelAnimating = true; levelDirection = -1;
                    // clear hover candidate
                    levelHovered = -1;
                    return;
                case SDL_SCANCODE_ESCAPE:
                    levelPanelAnimating = true; levelDirection = -1;
                    levelHovered = -1;
                    return;
                default: break;
            }
            // Move tentative cursor (don't commit to startLevelSelection yet)
            levelHovered = c;
            // Consume the event so main menu navigation does not also run
            return;
        }

        // Coop setup panel navigation (modal within the menu)
        if ((coopSetupVisible || coopSetupAnimating) && coopSetupTransition > 0.0) {
            switch (e.key.scancode) {
                case SDL_SCANCODE_LEFT:
                    coopSetupSelected = 0;
                    buttonFlash = 1.0;
                    return;
                case SDL_SCANCODE_RIGHT:
                    coopSetupSelected = 1;
                    buttonFlash = 1.0;
                    return;
                // Explicitly consume Up/Down so main menu navigation doesn't trigger
                case SDL_SCANCODE_UP:
                case SDL_SCANCODE_DOWN:
                    return;
                case SDL_SCANCODE_ESCAPE:
                    // Close coop panel without restarting music
                    showCoopSetupPanel(false, false);
                    return;
                case SDL_SCANCODE_RETURN:
                case SDL_SCANCODE_KP_ENTER:
                case SDL_SCANCODE_SPACE:
                {
                    const bool useAI = (coopSetupSelected == 1);
                    if (ctx.coopVsAI) {
                        *ctx.coopVsAI = useAI;
                    }
                    if (ctx.game) {
                        ctx.game->setMode(GameMode::Cooperate);
                        ctx.game->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
                    }
                    if (ctx.coopGame) {
                        ctx.coopGame->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
                    }
                    showCoopSetupPanel(false, false);
                    triggerPlay();
                    return;
                }
                default:
                    break;
            }
        }

        switch (e.key.scancode) {
            case SDL_SCANCODE_LEFT:
            case SDL_SCANCODE_UP:
                {
                const int total = MENU_BTN_COUNT;
                selectedButton = (selectedButton + total - 1) % total;
                // brief bright flash on navigation
                buttonFlash = 1.0;
                break;
            }
            case SDL_SCANCODE_RIGHT:
            case SDL_SCANCODE_DOWN:
                {
                const int total = MENU_BTN_COUNT;
                selectedButton = (selectedButton + 1) % total;
                // brief bright flash on navigation
                buttonFlash = 1.0;
                break;
            }
            case SDL_SCANCODE_RETURN:
            case SDL_SCANCODE_KP_ENTER:
            case SDL_SCANCODE_SPACE:
                if (!ctx.stateManager) {
                    break;
                }
                switch (selectedButton) {
                    case 0:
                        // Endless play
                        if (ctx.game) ctx.game->setMode(GameMode::Endless);
                        triggerPlay();
                        break;
                    case 1:
                        // Cooperative play: open setup panel (2P vs AI)
                        showCoopSetupPanel(true);
                        break;
                    case 2:
                        // Start challenge run at level 1
                        if (ctx.game) {
                            ctx.game->setMode(GameMode::Challenge);
                            if (ctx.skipNextLevelUpJingle) {
                                *ctx.skipNextLevelUpJingle = true;
                            }
                            ctx.game->startChallengeRun(1);
                        }
                        triggerPlay();
                        break;
                    case 3:
                        // Toggle inline level selector HUD (show/hide)
                        if (!levelPanelVisible && !levelPanelAnimating) {
                            levelPanelAnimating = true;
                            levelDirection = 1; // show
                            // initialize tentative cursor to current selected level
                            levelHovered = levelSelected < 0 ? 0 : levelSelected;
                        } else if (levelPanelVisible && !levelPanelAnimating) {
                            levelPanelAnimating = true;
                            levelDirection = -1; // hide
                        }
                        break;
                    case 4:
                        // Toggle the options panel with an animated slide-in/out.
                        if (!optionsVisible && !optionsAnimating) {
                            optionsAnimating = true;
                            optionsDirection = 1; // show
                        } else if (optionsVisible && !optionsAnimating) {
                            optionsAnimating = true;
                            optionsDirection = -1; // hide
                        }
                        break;
                    case 5:
                        // Toggle the inline HELP HUD (show/hide)
                        if (!helpPanelVisible && !helpPanelAnimating) {
                            helpPanelAnimating = true;
                            helpDirection = 1; // show
                            helpScroll = 0.0;
                        } else if (helpPanelVisible && !helpPanelAnimating) {
                            helpPanelAnimating = true;
                            helpDirection = -1; // hide
                        }
                        break;
                    case 6:
                        // Toggle the inline ABOUT HUD (show/hide)
                        if (!aboutPanelVisible && !aboutPanelAnimating) {
                            aboutPanelAnimating = true;
                            aboutDirection = 1;
                        } else if (aboutPanelVisible && !aboutPanelAnimating) {
                            aboutPanelAnimating = true;
                            aboutDirection = -1;
                        }
                        break;
                    case 7:
                        // Show the inline exit HUD
                        if (!exitPanelVisible && !exitPanelAnimating) {
                            exitPanelAnimating = true;
                            exitDirection = 1;
                            exitSelectedButton = 1;
                            if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = true;
                            if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
                        }
                        break;
                }
                break;
            case SDL_SCANCODE_ESCAPE:
                if (coopSetupVisible && !coopSetupAnimating) {
                    showCoopSetupPanel(false, false);
                    return;
                }
                // If options panel is visible, hide it first.
                if (optionsVisible && !optionsAnimating) {
                    optionsAnimating = true;
                    optionsDirection = -1;
                    return;
                }
                // Show inline exit HUD on ESC
                if (!exitPanelVisible && !exitPanelAnimating) {
                    exitPanelAnimating = true;
                    exitDirection = 1;
                    exitSelectedButton = 1;
                    if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = true;
                    if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
                }
                break;
            default:
                break;
        }
    }
}

void MenuState::update(double frameMs) {
    // Transient network status message (e.g., disconnect) shown on return to menu.
    if (ctx.coopNetUiStatusRemainingMs > 0.0) {
        ctx.coopNetUiStatusRemainingMs -= frameMs;
        if (ctx.coopNetUiStatusRemainingMs <= 0.0) {
            ctx.coopNetUiStatusRemainingMs = 0.0;
            ctx.coopNetUiStatusText.clear();
        }
    }

    // Update logo animation counter
    GlobalState::instance().logoAnimCounter += frameMs;
    // Advance options panel animation if active
    if (optionsAnimating) {
        double delta = (frameMs / optionsTransitionDurationMs) * static_cast<double>(optionsDirection);
        optionsTransition += delta;
        if (optionsTransition >= 1.0) {
            optionsTransition = 1.0;
            optionsVisible = true;
            optionsAnimating = false;
        } else if (optionsTransition <= 0.0) {
            optionsTransition = 0.0;
            optionsVisible = false;
            optionsAnimating = false;
        }
    }

    // Advance level panel animation if active
    if (levelPanelAnimating) {
        double delta = (frameMs / levelTransitionDurationMs) * static_cast<double>(levelDirection);
        levelTransition += delta;
        if (levelTransition >= 1.0) {
            levelTransition = 1.0;
            levelPanelVisible = true;
            levelPanelAnimating = false;
        } else if (levelTransition <= 0.0) {
            levelTransition = 0.0;
            levelPanelVisible = false;
            levelPanelAnimating = false;
        }
    }

    // Advance exit panel animation if active
    if (exitPanelAnimating) {
        double delta = (frameMs / exitTransitionDurationMs) * static_cast<double>(exitDirection);
        exitTransition += delta;
        if (exitTransition >= 1.0) {
            exitTransition = 1.0;
            exitPanelVisible = true;
            exitPanelAnimating = false;
        } else if (exitTransition <= 0.0) {
            exitTransition = 0.0;
            exitPanelVisible = false;
            exitPanelAnimating = false;
        }
    }

    // Advance help panel animation if active
    if (helpPanelAnimating) {
        double delta = (frameMs / helpTransitionDurationMs) * static_cast<double>(helpDirection);
        helpTransition += delta;
        if (helpTransition >= 1.0) {
            helpTransition = 1.0;
            helpPanelVisible = true;
            helpPanelAnimating = false;
        } else if (helpTransition <= 0.0) {
            helpTransition = 0.0;
            helpPanelVisible = false;
            helpPanelAnimating = false;
        }
    }

    // Advance about panel animation if active
    if (aboutPanelAnimating) {
        double delta = (frameMs / aboutTransitionDurationMs) * static_cast<double>(aboutDirection);
        aboutTransition += delta;
        if (aboutTransition >= 1.0) {
            aboutTransition = 1.0;
            aboutPanelVisible = true;
            aboutPanelAnimating = false;
        } else if (aboutTransition <= 0.0) {
            aboutTransition = 0.0;
            aboutPanelVisible = false;
            aboutPanelAnimating = false;
        }
    }

    // Advance coop setup panel animation if active
    if (coopSetupAnimating) {
        double delta = (frameMs / coopSetupTransitionDurationMs) * static_cast<double>(coopSetupDirection);
        coopSetupTransition += delta;
        if (coopSetupTransition >= 1.0) {
            coopSetupTransition = 1.0;
            coopSetupVisible = true;
            coopSetupAnimating = false;
        } else if (coopSetupTransition <= 0.0) {
            coopSetupTransition = 0.0;
            coopSetupVisible = false;
            coopSetupAnimating = false;
        }
    }

    // Animate level selection highlight position toward the selected cell center
    if (levelTransition > 0.0 && (lastLogicalScale > 0.0f)) {
        // Recompute same grid geometry used in render to find target center
        const float LOGICAL_W = 1200.f;
        const float LOGICAL_H = 1000.f;
        float winW = (float)lastLogicalVP.w;
        float winH = (float)lastLogicalVP.h;
        float contentOffsetX = 0.0f;
        float contentOffsetY = 0.0f;
        UIRenderer::computeContentOffsets(winW, winH, LOGICAL_W, LOGICAL_H, lastLogicalScale, contentOffsetX, contentOffsetY);

        const float PW = std::min(520.0f, LOGICAL_W * 0.65f);
        const float PH = std::min(360.0f, LOGICAL_H * 0.7f);
        float panelBaseX = (LOGICAL_W - PW) * 0.5f + contentOffsetX;
        float panelBaseY = (LOGICAL_H - PH) * 0.5f + contentOffsetY - (LOGICAL_H * 0.10f);
        float marginX = 34.0f, marginY = 56.0f;
        SDL_FRect area{ panelBaseX + marginX, panelBaseY + marginY, PW - 2.0f * marginX, PH - marginY - 28.0f };
        const int cols = 4, rows = 5;
        const float gapX = 12.0f, gapY = 12.0f;
        float cellW = (area.w - (cols - 1) * gapX) / cols;
        float cellH = (area.h - (rows - 1) * gapY) / rows;

        int targetIdx = std::clamp((levelHovered >= 0 ? levelHovered : levelSelected), 0, 19);
        int tr = targetIdx / cols, tc = targetIdx % cols;
        double targetX = area.x + tc * (cellW + gapX) + cellW * 0.5f;
        double targetY = area.y + tr * (cellH + gapY) + cellH * 0.5f;

        if (!levelHighlightInitialized) {
            levelHighlightX = targetX;
            levelHighlightY = targetY;
            levelHighlightInitialized = true;
        } else {
            // Exponential smoothing: alpha = 1 - exp(-k * dt)
            double k = levelHighlightSpeed; // user-tunable speed constant
            double alpha = 1.0 - std::exp(-k * frameMs);
            if (alpha < 1e-6) alpha = std::min(1.0, frameMs * 0.02);
            levelHighlightX += (targetX - levelHighlightX) * alpha;
            levelHighlightY += (targetY - levelHighlightY) * alpha;
        }
    }

        // Update pulsing animation (used for PLAY emphasis)
        if (buttonPulseEnabled) {
            buttonPulseTime += frameMs;
            double t = (buttonPulseTime * 0.001) * buttonPulseSpeed; // seconds * speed
            double s = 0.0;
            switch (buttonPulseEasing) {
                case 0: // sin
                    s = (std::sin(t * 2.0 * 3.14159265358979323846) * 0.5) + 0.5;
                    break;
                case 1: // triangle
                {
                    double ft = t - std::floor(t);
                    s = (ft < 0.5) ? (ft * 2.0) : (2.0 - ft * 2.0);
                    break;
                }
                case 2: // exponential ease-in-out (normalized)
                {
                    double ft = t - std::floor(t);
                    if (ft < 0.5) {
                        s = 0.5 * std::pow(2.0, 20.0 * ft - 10.0);
                    } else {
                        s = 1.0 - 0.5 * std::pow(2.0, -20.0 * ft + 10.0);
                    }
                    // Clamp to 0..1 in case of numeric issues
                    if (s < 0.0) s = 0.0; if (s > 1.0) s = 1.0;
                    break;
                }
                default:
                    s = (std::sin(t * 2.0 * 3.14159265358979323846) * 0.5) + 0.5;
            }
            buttonPulseAlpha = buttonPulseMinAlpha + s * (buttonPulseMaxAlpha - buttonPulseMinAlpha);
        } else {
            buttonPulseAlpha = 1.0;
        }

        // Keep the base group alpha stable; pulsing is applied selectively in the renderer.
        buttonGroupAlpha = 1.0;

        // Update flash decay
        if (buttonFlash > 0.0) {
            buttonFlash -= frameMs * buttonFlashDecay;
            if (buttonFlash < 0.0) buttonFlash = 0.0;
        }
}

void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
    // Use fixed logical dimensions to match main.cpp and ensure consistent layout
    // This prevents the UI from floating apart on wide/tall screens
    const float LOGICAL_W = 1200.f;
    const float LOGICAL_H = 1000.f;
    
    // Trace entry to persistent log for debugging abrupt exit/crash during render
    {
        FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render entry\n"); fclose(f); }
    }
    
    // Compute content offsets (same approach as main.cpp for proper centering)
    float winW = (float)logicalVP.w;
    float winH = (float)logicalVP.h;
    float contentOffsetX = 0.0f;
    float contentOffsetY = 0.0f;
    UIRenderer::computeContentOffsets(winW, winH, LOGICAL_W, LOGICAL_H, logicalScale, contentOffsetX, contentOffsetY);

    // Cache logical viewport/scale for update() so it can compute HUD target positions
    lastLogicalScale = logicalScale;
    lastLogicalVP = logicalVP;

    // Background is drawn by main (stretched to the full window) to avoid double-draw.
    {
        FILE* f = fopen("spacetris_trace.log", "a");
        if (f) {
            fprintf(f, "MenuState::render ctx.mainScreenTex=%llu (w=%d h=%d)\n",
                (unsigned long long)(uintptr_t)ctx.mainScreenTex,
                ctx.mainScreenW,
                ctx.mainScreenH);
            fclose(f);
        }
    }

    FontAtlas* useFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
    // Slide-space amount for the options HUD (how much highscores move)
    const float moveAmount = 420.0f; // increased so lower score rows slide further up

    // Compute eased transition and delta to shift highscores when either options, level, or exit HUD is shown.
    // Exclude coopSetupTransition from the highscores slide so opening the
    // COOPERATE setup does not shift the highscores panel upward.
    float combinedTransition = static_cast<float>(std::max(
        std::max(std::max(optionsTransition, levelTransition), exitTransition),
        std::max(helpTransition, aboutTransition)
    ));
    float eased = combinedTransition * combinedTransition * (3.0f - 2.0f * combinedTransition); // cubic smoothstep
    float panelDelta = eased * moveAmount;

    // Draw a larger centered logo above the highscores area, then a small "TOP PLAYER" label
    // Move the whole block slightly up to better match the main screen overlay framing.
    float menuYOffset = LOGICAL_H * 0.03f; // same offset used for buttons
    float scoresYOffset = -LOGICAL_H * 0.05f;
    // Move logo and highscores upward by ~10% of logical height for better vertical balance
    float upwardShift = LOGICAL_H * 0.08f;
    float topPlayersY = LOGICAL_H * 0.20f + contentOffsetY - panelDelta + menuYOffset + scoresYOffset - upwardShift;
    float scoresStartY = topPlayersY;
    if (useFont) {
        // Preferred logo texture (full) if present, otherwise the small logo
        SDL_Texture* logoTex = ctx.logoTex ? ctx.logoTex : ctx.logoSmallTex;
        float logoDrawH = 72.0f; // larger logo height
        if (logoTex) {
            float texW = 0.0f, texH = 0.0f;
            SDL_GetTextureSize(logoTex, &texW, &texH);
            if (texW > 0.0f && texH > 0.0f) {
                float scale = logoDrawH / texH;
                float drawW = texW * scale;
                SDL_FRect dst{ (LOGICAL_W - drawW) * 0.5f + contentOffsetX, topPlayersY, drawW, logoDrawH };
                SDL_SetTextureAlphaMod(logoTex, 230);
                SDL_RenderTexture(renderer, logoTex, nullptr, &dst);
                // move down for title under logo
                scoresStartY = dst.y + dst.h + 8.0f;
            }
        }

        // Small label under the logo — show "COOPERATE" when coop setup is active
        const std::string smallTitle = (coopSetupAnimating || coopSetupVisible) ? "COOPERATE" : "TOP PLAYER";
        float titleScale = 0.9f;
        int tW = 0, tH = 0;
        useFont->measure(smallTitle, titleScale, tW, tH);
        float titleX = (LOGICAL_W - (float)tW) * 0.5f + contentOffsetX;
                useFont->draw(renderer, titleX, scoresStartY, smallTitle, titleScale, SDL_Color{200,220,230,255});
        scoresStartY += (float)tH + 12.0f;

        if (!ctx.coopNetUiStatusText.empty() && ctx.coopNetUiStatusRemainingMs > 0.0) {
            float msgScale = 0.75f;
            int mW = 0, mH = 0;
            useFont->measure(ctx.coopNetUiStatusText, msgScale, mW, mH);
            float msgX = (LOGICAL_W - (float)mW) * 0.5f + contentOffsetX;
            useFont->draw(renderer, msgX, scoresStartY, ctx.coopNetUiStatusText, msgScale, SDL_Color{255, 224, 130, 255});
            scoresStartY += (float)mH + 10.0f;
        }
    }
    static const std::vector<ScoreEntry> EMPTY_SCORES;
    const auto& hs = ctx.scores ? ctx.scores->all() : EMPTY_SCORES;
    // Choose which game_type to show based on current menu selection or mouse hover.
    // Prefer `hoveredButton` (mouse-over) when available so the TOP PLAYER panel
    // updates responsively while the user moves the pointer over the bottom menu.
    int activeBtn = (ctx.hoveredButton ? *ctx.hoveredButton : -1);
    if (activeBtn < 0) activeBtn = selectedButton;
    std::string wantedType = "classic";
    if (activeBtn == 0) wantedType = "classic"; // Play / Endless
    else if (activeBtn == 1) wantedType = "cooperate"; // Coop
    else if (activeBtn == 2) wantedType = "challenge"; // Challenge
    // Filter highscores to the desired game type
    std::vector<ScoreEntry> filtered;
    filtered.reserve(hs.size());
    for (const auto &e : hs) {
        if (e.gameType == wantedType) filtered.push_back(e);
    }
    size_t maxDisplay = std::min(filtered.size(), size_t(10)); // display only top 10

    // Draw highscores as an inline HUD-like panel (no opaque box), matching Options/Level/Exit style
    // Keep highscores visible while the coop setup is animating; hide them only
    // once the coop setup is fully visible so the buttons can appear afterward.
    if (useFont && !coopSetupVisible) {
        const float panelW = (wantedType == "cooperate") ? std::min(920.0f, LOGICAL_W * 0.92f) : std::min(780.0f, LOGICAL_W * 0.85f);
        const float panelH = 36.0f + maxDisplay * 36.0f; // header + rows
        // Shift the entire highscores panel slightly left (~1.5% of logical width)
        float panelShift = LOGICAL_W * 0.015f;
        float panelBaseX = (LOGICAL_W - panelW) * 0.5f + contentOffsetX - panelShift;
        float panelBaseY = scoresStartY - 20.0f - panelDelta; // nudge up and apply HUD slide

        // Column positions inside panel
        float colLeft = panelBaseX + 12.0f;
        float colWidth = panelW - 24.0f;
        // Center the column group inside the panel and place columns relative to center
        float centerX = panelBaseX + panelW * 0.5f;
        // Tighter column spacing: compress multipliers around center
        float rankX = centerX - colWidth * 0.34f;
        // Move PLAYER column a bit further left while leaving others unchanged
        float nameX = (wantedType == "cooperate") ? centerX - colWidth * 0.30f : centerX - colWidth * 0.25f;
        // Move SCORE column slightly left for tighter layout (adjusted for coop)
        float scoreX = (wantedType == "cooperate") ? centerX - colWidth * 0.02f : centerX - colWidth * 0.06f;
        float linesX = centerX + colWidth * 0.14f;
        float levelX = centerX + colWidth * 0.26f;
        float timeX = centerX + colWidth * 0.38f;

        // Column header labels
        float headerY = panelBaseY + 26.0f;
        // Slightly smaller header for compactness
        float headerScale = 0.75f;
        // Use same color as Options heading (use full alpha for maximum brightness)
        SDL_Color headerColor = SDL_Color{120,220,255,255};
        useFont->draw(renderer, rankX, headerY, "#", headerScale, headerColor);
        useFont->draw(renderer, nameX, headerY, (wantedType == "cooperate") ? "PLAYERS" : "PLAYER", headerScale, headerColor);
        useFont->draw(renderer, scoreX, headerY, "SCORE", headerScale, headerColor);
        useFont->draw(renderer, linesX, headerY, "LINES", headerScale, headerColor);
        useFont->draw(renderer, levelX, headerY, "LVL", headerScale, headerColor);
        useFont->draw(renderer, timeX, headerY, "TIME", headerScale, headerColor);

        const float rowHeight = 28.0f;
        float rowY = panelBaseY + 28.0f;
        float rowScale = 0.80f;

        for (size_t i = 0; i < maxDisplay; ++i) {
            float y = rowY + i * rowHeight;

            // (removed thin blue separator between rows per request)

            // Subtle highlight wave for the list similar to before
            float wave = std::sin((float)GlobalState::instance().logoAnimCounter * 0.006f + i * 0.25f) * 4.0f;

            // Per-row entrance staggering and easing to make movement fancier
            const float baseEntrance = 40.0f;            // pixels rows slide from
            const float perRowDelay = 0.06f;            // stagger delay per row (in eased 0..1 space)
            float rowDelay = perRowDelay * (float)i;
            float rowT = 0.0f;
            if (eased > rowDelay) rowT = (eased - rowDelay) / (1.0f - rowDelay);
            if (rowT < 0.0f) rowT = 0.0f; if (rowT > 1.0f) rowT = 1.0f;
            // cubic smoothstep per row
            float rowEase = rowT * rowT * (3.0f - 2.0f * rowT);
            float entryOffset = (1.0f - rowEase) * baseEntrance;
            // Slight alpha fade during entrance
            float alphaMul = 1;

            // Slight scale slip per row (keeps earlier visual taper)
            float curRowScale = rowScale - std::min(0.20f, 0.05f * (float)i);

            // Base row color matches header brightness; top 3 get vivid medal colors
            SDL_Color baseRowColor = SDL_Color{ headerColor.r, headerColor.g, headerColor.b, 255 };
            if (i == 0) {
                baseRowColor = SDL_Color{255,215,0,255}; // bright gold
            } else if (i == 1) {
                baseRowColor = SDL_Color{230,230,235,255}; // bright silver
            } else if (i == 2) {
                baseRowColor = SDL_Color{255,165,80,255}; // brighter bronze/orange
            }
            SDL_Color rowColor = baseRowColor;
            // Use entrance alpha to fade in but keep RGB full-brightness; map alphaMul to 0..1
            rowColor.a = static_cast<Uint8>(std::round(255.0f * alphaMul));

            // horizontal subtle slide for name column to add a little polish
            float nameXAdj = nameX - (1.0f - rowEase) * 8.0f;

            char rankStr[8]; std::snprintf(rankStr, sizeof(rankStr), "%zu.", i + 1);
            useFont->draw(renderer, rankX, y + wave + entryOffset, rankStr, curRowScale, rowColor);

            useFont->draw(renderer, nameXAdj, y + wave + entryOffset, filtered[i].name, curRowScale, rowColor);

            char scoreStr[16]; std::snprintf(scoreStr, sizeof(scoreStr), "%d", filtered[i].score);
            useFont->draw(renderer, scoreX, y + wave + entryOffset, scoreStr, curRowScale, rowColor);

            char linesStr[8]; std::snprintf(linesStr, sizeof(linesStr), "%d", filtered[i].lines);
            useFont->draw(renderer, linesX, y + wave + entryOffset, linesStr, curRowScale, rowColor);

            char levelStr[8]; std::snprintf(levelStr, sizeof(levelStr), "%d", filtered[i].level);
            useFont->draw(renderer, levelX, y + wave + entryOffset, levelStr, curRowScale, rowColor);

            char timeStr[16]; int mins = int(filtered[i].timeSec) / 60; int secs = int(filtered[i].timeSec) % 60;
            std::snprintf(timeStr, sizeof(timeStr), "%d:%02d", mins, secs);
            useFont->draw(renderer, timeX, y + wave + entryOffset, timeStr, curRowScale, rowColor);
        }
    }

    // The main_screen overlay is drawn by main.cpp as the background
    // We don't need to draw it again here as a logo

    // Draw bottom action buttons with responsive sizing (reduced to match main mouse hit-test)
    // Use the contentW calculated at the top with content offsets
    float contentW = LOGICAL_W * logicalScale;
    bool isSmall = (contentW < 700.0f);
    // Adjust button dimensions to match the background button graphics
    float btnW = 200.0f; // Fixed width to match background buttons
    float btnH = 70.0f;  // Fixed height to match background buttons
    float btnX = LOGICAL_W * 0.5f + contentOffsetX;
    // Adjust vertical position to align with background buttons; move slightly down
    float btnY = LOGICAL_H * 0.865f + contentOffsetY + (LOGICAL_H * 0.02f) + menuYOffset;

    if (ctx.pixelFont) {
        {
            FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render drawing buttons; pixelFont=%llu\n", (unsigned long long)(uintptr_t)ctx.pixelFont); fclose(f); }
        }
        char levelBtnText[32];
        int startLevel = ctx.startLevelSelection ? *ctx.startLevelSelection : 0;
        std::snprintf(levelBtnText, sizeof(levelBtnText), "LEVEL %d", startLevel);

        struct MenuButtonDef {
            SDL_Color bg;
            SDL_Color border;
            std::string label;
        };

        std::array<MenuButtonDef,5> buttons = {
            MenuButtonDef{ SDL_Color{60,180,80,255}, SDL_Color{30,120,40,255}, "PLAY" },
            MenuButtonDef{ SDL_Color{40,140,240,255}, SDL_Color{20,100,200,255}, levelBtnText },
            MenuButtonDef{ SDL_Color{130,80,210,255}, SDL_Color{90,40,170,255}, "OPTIONS" },
            MenuButtonDef{ SDL_Color{200,200,60,255}, SDL_Color{150,150,40,255}, "HELP" },
            MenuButtonDef{ SDL_Color{200,70,70,255}, SDL_Color{150,40,40,255}, "EXIT" }
        };

        // Icon array (nullptr if icon not loaded)
        std::array<SDL_Texture*, 5> icons = {
            playIcon,
            levelIcon,
            optionsIcon,
            helpIcon,
            exitIcon
        };

        // Fixed spacing to match background button positions
        float spacing = isSmall ? btnW * 1.2f : btnW * 1.15f;

        // Draw each button individually so each can have its own coordinates
        if (drawMainButtonNormally) {
            // Draw semi-transparent background panel behind the full button group
            {
                float groupCenterX = btnX;
                float halfSpan = 2.0f * spacing;
                float panelLeft = groupCenterX - halfSpan - btnW * 0.5f - 14.0f;
                float panelRight = groupCenterX + halfSpan + btnW * 0.5f + 14.0f;
                float panelTop = btnY - btnH * 0.5f - 12.0f;
                float panelH = btnH + 24.0f;
                SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
                // Backdrop blur pass before tint (use captured scene texture if available)
                renderBackdropBlur(renderer, logicalVP, logicalScale, panelTop, panelH, ctx.sceneTex, ctx.sceneW, ctx.sceneH);
                // Brighter, less-opaque background to increase contrast (match top path)
                SDL_SetRenderDrawColor(renderer, 28, 36, 46, 180);
                // Fill full-width background so edges are covered in fullscreen
                float viewportLogicalW = (float)logicalVP.w / logicalScale;
                SDL_FRect fullPanel{ 0.0f, panelTop, viewportLogicalW, panelH };
                SDL_RenderFillRect(renderer, &fullPanel);
                // Also draw the central strip to keep visual center emphasis
                SDL_FRect panelRect{ panelLeft, panelTop, panelRight - panelLeft, panelH };
                SDL_RenderFillRect(renderer, &panelRect);
                // subtle border across full logical width
                SDL_SetRenderDrawColor(renderer, 120, 140, 160, 200);
                // Expand border to cover full window width (use actual viewport)
                SDL_FRect borderFull{ 0.0f, panelTop, viewportLogicalW, panelH };
                SDL_RenderRect(renderer, &borderFull);
            }
            // Button 0 - PLAY
            {
                const int i = 0;
                float cxCenter = 0.0f;
                float cyCenter = btnY;
                if (ctx.menuButtonsExplicit) {
                    cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
                    cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
                } else {
                    float offset = (static_cast<float>(i) - 1.5f) * spacing;
                    cxCenter = btnX + offset + 15.0f;
                }
                UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
                                      buttons[i].label, false, selectedButton == i,
                                      buttons[i].bg, buttons[i].border, true, icons[i]);
                // no per-button neon outline; group background handles emphasis
            }

            // Button 1 - LEVEL
            {
                const int i = 1;
                float cxCenter = 0.0f;
                float cyCenter = btnY;
                if (ctx.menuButtonsExplicit) {
                    cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
                    cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
                } else {
                    float offset = (static_cast<float>(i) - 1.5f) * spacing;
                    cxCenter = btnX + offset;
                }
                UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
                                      buttons[i].label, false, selectedButton == i,
                                      buttons[i].bg, buttons[i].border, true, icons[i]);
            }

            // Button 2 - OPTIONS
            {
                const int i = 2;
                float cxCenter = 0.0f;
                float cyCenter = btnY;
                if (ctx.menuButtonsExplicit) {
                    cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
                    cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
                } else {
                    float offset = (static_cast<float>(i) - 1.5f) * spacing;
                    cxCenter = btnX + offset - 24.0f;
                }
                UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
                                      buttons[i].label, false, selectedButton == i,
                                      buttons[i].bg, buttons[i].border, true, icons[i]);
            }

            // Button 3 - EXIT
            {
                const int i = 3;
                float cxCenter = 0.0f;
                float cyCenter = btnY;
                if (ctx.menuButtonsExplicit) {
                    cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
                    cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
                } else {
                    float offset = (static_cast<float>(i) - 2.0f) * spacing;
                    cxCenter = btnX + offset;
                }
                UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
                                      buttons[i].label, false, selectedButton == i,
                                      buttons[i].bg, buttons[i].border, true, icons[i]);

                // Button 4 - EXIT
                {
                    const int i = 4;
                    float cxCenter = 0.0f;
                    float cyCenter = btnY;
                    if (ctx.menuButtonsExplicit) {
                        cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
                        cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
                    } else {
                        float offset = (static_cast<float>(i) - 2.0f) * spacing;
                        cxCenter = btnX + offset - 24.0f;
                    }
                    UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
                                          buttons[i].label, false, selectedButton == i,
                                          buttons[i].bg, buttons[i].border, true, icons[i]);
                }
            }
        }
    }

    // Inline COOP choice buttons: when COOPERATE is selected show two large
    // choice buttons in the highscores panel area (top of the screen).
    // coopSetupRectsValid is cleared each frame and set to true when buttons are drawn
    coopSetupRectsValid = false;
    // Draw the inline COOP choice buttons as soon as the coop setup starts
    // animating or is visible. Highscores are no longer slid upward when
    // the setup opens, so the buttons can show immediately.
    if (coopSetupAnimating || coopSetupVisible) {
        // Recompute panel geometry matching highscores layout above so buttons
        // appear centered inside the same visual area.
        const float panelW = std::min(920.0f, LOGICAL_W * 0.92f);
        const float panelShift = LOGICAL_W * 0.015f;
        const float panelBaseX = (LOGICAL_W - panelW) * 0.5f + contentOffsetX - panelShift;
        const float panelH = 36.0f + maxDisplay * 36.0f; // same as highscores panel
        // Highscores are animated upward by `panelDelta` while opening the coop setup.
        // We want the choice buttons to appear *after* that scroll, in the original
        // highscores area (not sliding offscreen with the scores).
        const float panelBaseY = scoresStartY - 20.0f;

        // Choice buttons (partner selection) and nested network host/join UI
        const float btnH2 = 60.0f;
        const float gap = 34.0f;
        const float btnW2 = std::min(280.0f, (panelW - gap * 2.0f) / 3.0f);
        const float totalChoiceW = btnW2 * 3.0f + gap * 2.0f;
        // Shift the image and buttons slightly for layout balance
        const float shiftX = 20.0f;
        const float bx = panelBaseX + (panelW - totalChoiceW) * 0.5f + shiftX;
        // Move the buttons up by ~80px to sit closer under the logo
        const float by = panelBaseY + (panelH - btnH2) * 0.5f - 80.0f;

        coopSetupBtnRects[0] = SDL_FRect{ bx, by, btnW2, btnH2 };
        coopSetupBtnRects[1] = SDL_FRect{ bx + (btnW2 + gap), by, btnW2, btnH2 };
        coopSetupBtnRects[2] = SDL_FRect{ bx + (btnW2 + gap) * 2.0f, by, btnW2, btnH2 };
        coopSetupRectsValid = true;

        SDL_Color bg{ 24, 36, 52, 220 };
        SDL_Color border{ 110, 200, 255, 220 };

        // Load coop info image once when the coop setup is first shown
        if (!coopInfoTexture) {
            const std::string resolved = AssetPath::resolveImagePath("assets/images/cooperate_info.png");
            if (!resolved.empty()) {
                SDL_Surface* surf = IMG_Load(resolved.c_str());
                if (surf) {
                    // Save dimensions from surface then create texture
                    coopInfoTexW = surf->w;
                    coopInfoTexH = surf->h;
                    coopInfoTexture = SDL_CreateTextureFromSurface(renderer, surf);
                    SDL_DestroySurface(surf);
                } else {
                    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "MenuState: failed to load %s: %s", resolved.c_str(), SDL_GetError());
                }
            }
        }

        // If the image loaded, render it centered above the choice buttons
        // Compute fade alpha from the coop transition so it can be used for image, text and buttons
        float alphaFactor = static_cast<float>(coopSetupTransition);
        if (alphaFactor < 0.0f) alphaFactor = 0.0f;
        if (alphaFactor > 1.0f) alphaFactor = 1.0f;
        // Compute coop info image placement (draw as background for both ChoosePartner and Network steps)
        float imgX = 0.0f, imgY = 0.0f, targetW = 0.0f, targetH = 0.0f;
        bool hasCoopImg = false;
        if (coopInfoTexture && coopInfoTexW > 0 && coopInfoTexH > 0) {
            float totalW = totalChoiceW;
            // Keep coop info image slightly smaller than the button row.
            // Use a modest scale so it doesn't dominate the UI.
            float maxImgW = totalW * 0.65f;
            targetW = std::min(maxImgW, static_cast<float>(coopInfoTexW));
            float scale = targetW / static_cast<float>(coopInfoTexW);
            targetH = static_cast<float>(coopInfoTexH) * scale;
            imgX = bx + (totalW - targetW) * 0.5f;
            imgY = by - targetH - 8.0f; // keep the small gap above buttons
            float minY = panelBaseY + 6.0f;
            if (imgY < minY) imgY = minY;
            SDL_FRect dst{ imgX, imgY, targetW, targetH };
            SDL_SetTextureBlendMode(coopInfoTexture, SDL_BLENDMODE_BLEND);
            // Make the coop info image slightly transparent scaled by transition
            SDL_SetTextureAlphaMod(coopInfoTexture, static_cast<Uint8>(std::round(200.0f * alphaFactor)));
            SDL_RenderTexture(renderer, coopInfoTexture, nullptr, &dst);
            hasCoopImg = true;

            // Only draw the instructional overlay text when choosing partner.
            if (coopSetupStep == CoopSetupStep::ChoosePartner) {
                FontAtlas* f = ctx.font ? ctx.font : ctx.pixelFont;
                if (f) {
                    const float pad = 38.0f;
                    float textX = panelBaseX + pad;
                    // Position the text over the lower portion of the image (overlay)
                    // Move the block upward by ~150px to match UI request
                    float textY = imgY + targetH - std::min(80.0f, targetH * 0.35f) - 150.0f;

                    // Bulleted list (measure sample line height first)
                    const std::vector<std::string> bullets = {
                        "The playfield is shared between two players",
                        "Each player controls one half of the grid",
                        "A line clears only when both halves are filled",
                        "Timing and coordination are essential"
                    };
                    float bulletScale = 0.78f;
                    SDL_Color bulletCol{200,220,230,220};
                    bulletCol.a = static_cast<Uint8>(std::round(bulletCol.a * alphaFactor));
                    int sampleLW = 0, sampleLH = 0;
                    f->measure(bullets[0], bulletScale, sampleLW, sampleLH);

                // Header: move it up by one sample row so it sits higher
                const std::string header = "* HOW TO PLAY – COOPERATE MODE *";
                float headerScale = 0.95f;
                int hW=0, hH=0; f->measure(header, headerScale, hW, hH);
                float hx = panelBaseX + (panelW - static_cast<float>(hW)) * 0.5f + 40.0f; // nudge header right by 40px
                float headerY = textY - static_cast<float>(sampleLH);
                SDL_Color headerCol = SDL_Color{220,240,255,230}; headerCol.a = static_cast<Uint8>(std::round(headerCol.a * alphaFactor));
                f->draw(renderer, hx, headerY, header, headerScale, headerCol);
                // Start body text slightly below header
                textY = headerY + static_cast<float>(hH) + 8.0f;

                // Shift non-header text to the right by 100px and down by 20px
                float bulletX = textX + 200.0f;
                textY += 20.0f;
                for (const auto &line : bullets) {
                    std::string withBullet = std::string("• ") + line;
                    f->draw(renderer, bulletX, textY, withBullet, bulletScale, bulletCol);
                    int lw=0, lH=0; f->measure(withBullet, bulletScale, lw, lH);
                    textY += static_cast<float>(lH) + 6.0f;
                }

                // GOAL section (aligned with shifted bullets)
                textY += 6.0f;
                std::string goalTitle = "GOAL:";
                SDL_Color goalTitleCol = SDL_Color{255,215,80,230}; goalTitleCol.a = static_cast<Uint8>(std::round(goalTitleCol.a * alphaFactor));
                f->draw(renderer, bulletX, textY, goalTitle, 0.88f, goalTitleCol);
                int gW=0, gH=0; f->measure(goalTitle, 0.88f, gW, gH);
                float goalX = bulletX + static_cast<float>(gW) + 10.0f;
                std::string goalText = "Clear lines together and achieve the highest TEAM SCORE";
                SDL_Color goalTextCol = SDL_Color{220,240,255,220}; goalTextCol.a = static_cast<Uint8>(std::round(goalTextCol.a * alphaFactor));
                f->draw(renderer, goalX, textY, goalText, 0.86f, goalTextCol);
            }
        }
        }

        // Delay + eased fade specifically for the two coop buttons so they appear after the image/text.
        const float btnDelay = 0.25f; // fraction of transition to wait before buttons start fading
        float rawBtn = (alphaFactor - btnDelay) / (1.0f - btnDelay);
        rawBtn = std::clamp(rawBtn, 0.0f, 1.0f);
        // ease-in (squared) for a slower, smoother fade
        float buttonFade = rawBtn * rawBtn;
        SDL_Color bgA = bg; bgA.a = static_cast<Uint8>(std::round(bgA.a * buttonFade));
        SDL_Color borderA = border; borderA.a = static_cast<Uint8>(std::round(borderA.a * buttonFade));

        // Step 1: choose partner mode
        if (coopSetupStep == CoopSetupStep::ChoosePartner) {
            UIRenderer::drawButton(renderer, ctx.pixelFont,
                coopSetupBtnRects[0].x + btnW2 * 0.5f,
                coopSetupBtnRects[0].y + btnH2 * 0.5f,
                btnW2, btnH2,
                "LOCAL CO-OP",
                false,
                coopSetupSelected == 0,
                bgA,
                borderA,
                false,
                nullptr);

            UIRenderer::drawButton(renderer, ctx.pixelFont,
                coopSetupBtnRects[1].x + btnW2 * 0.5f,
                coopSetupBtnRects[1].y + btnH2 * 0.5f,
                btnW2, btnH2,
                "AI PARTNER",
                false,
                coopSetupSelected == 1,
                bgA,
                borderA,
                false,
                nullptr);

            UIRenderer::drawButton(renderer, ctx.pixelFont,
                coopSetupBtnRects[2].x + btnW2 * 0.5f,
                coopSetupBtnRects[2].y + btnH2 * 0.5f,
                btnW2, btnH2,
                "2 PLAYER (NET)",
                false,
                coopSetupSelected == 2,
                bgA,
                borderA,
                false,
                nullptr);
        }

        // Step 2: network host/join selection and address entry
        if (coopSetupStep == CoopSetupStep::NetworkChooseRole || coopSetupStep == CoopSetupStep::NetworkEnterAddress || coopSetupStep == CoopSetupStep::NetworkWaiting) {
            // Draw two buttons centered under the main row.
            const float roleBtnW = std::min(280.0f, panelW * 0.30f);
            const float roleGap = 48.0f;
            const float roleTotalW = roleBtnW * 2.0f + roleGap;
            const float roleX = panelBaseX + (panelW - roleTotalW) * 0.5f + shiftX;
            // Move the host/join buttons down from the previous higher position.
            // Shift down by one button height plus half a button (effectively lower them):
            const float roleY = by + (btnH2 * 0.5f) - 18.0f;

            SDL_FRect hostRect{ roleX, roleY, roleBtnW, btnH2 };
            SDL_FRect joinRect{ roleX + roleBtnW + roleGap, roleY, roleBtnW, btnH2 };

            UIRenderer::drawButton(renderer, ctx.pixelFont,
                hostRect.x + roleBtnW * 0.5f,
                hostRect.y + btnH2 * 0.5f,
                roleBtnW,
                btnH2,
                "HOST GAME",
                false,
                coopNetworkRoleSelected == 0,
                bgA,
                borderA,
                false,
                nullptr);

            UIRenderer::drawButton(renderer, ctx.pixelFont,
                joinRect.x + roleBtnW * 0.5f,
                joinRect.y + btnH2 * 0.5f,
                roleBtnW,
                btnH2,
                "JOIN GAME",
                false,
                coopNetworkRoleSelected == 1,
                bgA,
                borderA,
                false,
                nullptr);

            FontAtlas* f = ctx.font ? ctx.font : ctx.pixelFont;
            if (f) {
                SDL_Color infoCol{200, 220, 230, static_cast<Uint8>(std::round(220.0f * buttonFade))};
                // Draw connection info on separate lines and shift right by ~200px
                char portLine[64];
                std::snprintf(portLine, sizeof(portLine), "PORT %u", (unsigned)coopNetworkPort);
                char hostLine[128];
                std::snprintf(hostLine, sizeof(hostLine), "HOST IP %s", coopNetworkBindAddress.c_str());
                char joinLine[128];
                std::snprintf(joinLine, sizeof(joinLine), "JOIN IP %s", coopNetworkJoinAddress.c_str());

                const float textShiftX = 200.0f;
                const float textX = panelBaseX + 60.0f + textShiftX;
                const float endpointY = (hasCoopImg ? (imgY + targetH * 0.62f) : (roleY + btnH2 + 12.0f));
                const float lineSpacing = 28.0f;
                // Show only the minimal info needed for the selected role.
                f->draw(renderer, textX, endpointY, portLine, 0.90f, infoCol);
                if (coopNetworkRoleSelected == 0) {
                    // Host: show bind address only
                    f->draw(renderer, textX, endpointY + lineSpacing, hostLine, 0.90f, infoCol);
                } else {
                    // Client: show join target only
                    f->draw(renderer, textX, endpointY + lineSpacing, joinLine, 0.90f, infoCol);
                }

                float hintY = endpointY + lineSpacing * 2.0f + 6.0f;

                // Bottom helper prompt: show a compact instruction under the image window
                float bottomY = hasCoopImg ? (imgY + targetH + 18.0f) : (hintY + 36.0f);
                SDL_Color bottomCol{180,200,210,200};
                if (coopNetworkRoleSelected == 0) {
                    f->draw(renderer, textX, bottomY, "HOST: press ENTER to edit bind IP, then press ENTER to confirm", 0.82f, bottomCol);
                } else {
                    f->draw(renderer, textX, bottomY, "JOIN: press ENTER to type server IP, then press ENTER to connect", 0.82f, bottomCol);
                }
                if (coopSetupStep == CoopSetupStep::NetworkWaiting && !coopNetworkStatusText.empty()) {
                    SDL_Color statusCol{255, 215, 80, static_cast<Uint8>(std::round(240.0f * buttonFade))};
                    f->draw(renderer, textX, hintY, coopNetworkStatusText, 1.00f, statusCol);
                } else if (coopSetupStep == CoopSetupStep::NetworkEnterAddress) {
                    SDL_Color hintCol{160, 190, 210, static_cast<Uint8>(std::round(200.0f * buttonFade))};
                    const char* label = (coopNetworkRoleSelected == 0) ? "TYPE HOST IP (BIND) THEN ENTER" : "TYPE JOIN IP THEN ENTER";
                    f->draw(renderer, textX, hintY, label, 0.82f, hintCol);
                } else {
                    SDL_Color hintCol{160, 190, 210, static_cast<Uint8>(std::round(200.0f * buttonFade))};
                    f->draw(renderer, textX, hintY, "PRESS ENTER TO EDIT/CONFIRM   ESC TO GO BACK", 0.82f, hintCol);
                }
            }
        }
    }
    // NOTE: slide-up COOP panel intentionally removed. Only the inline
    // highscores-area choice buttons are shown when coop setup is active.

    // Inline exit HUD (no opaque background) - slides into the highscores area
    if (exitTransition > 0.0) {
        float easedE = static_cast<float>(exitTransition);
        easedE = easedE * easedE * (3.0f - 2.0f * easedE);
        const float panelW = 520.0f;
        const float panelH = 360.0f;
        float panelBaseX = (LOGICAL_W - panelW) * 0.5f + contentOffsetX;
        float panelBaseY = (LOGICAL_H - panelH) * 0.5f + contentOffsetY - (LOGICAL_H * 0.10f);
        float slideAmount = LOGICAL_H * 0.42f;
        float panelY = panelBaseY + (1.0f - easedE) * slideAmount;

        FontAtlas* titleFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
        if (titleFont) titleFont->draw(renderer, panelBaseX + 12.0f, panelY + 6.0f, "EXIT GAME?", 1.6f, SDL_Color{255,200,80,255});

        SDL_FRect area{ panelBaseX + 12.0f, panelY + 56.0f, panelW - 24.0f, panelH - 120.0f };
        // Sample long message (scrollable)
        // Paragraph-style lines for a nicer exit confirmation message
        std::vector<std::string> lines = {
            "Quit now to return to your desktop. Your current session will end.",
            "Press YES to quit immediately, or NO to return to the menu and continue playing.",
            "Adjust audio, controls and other settings anytime from the Options menu.",
            "Thanks for playing SPACETRIS — we hope to see you again!"
        };

        // Draw scrollable text (no background box; increased line spacing)
        FontAtlas* f = ctx.font ? ctx.font : ctx.pixelFont;
        float y = area.y - (float)exitScroll;
        const float lineSpacing = 34.0f; // increased spacing for readability
                if (f) {
            for (size_t i = 0; i < lines.size(); ++i) {
                f->draw(renderer, area.x + 6.0f, y + i * lineSpacing, lines[i], 1.0f, SDL_Color{200,220,240,255});
            }
        }

        // Draw buttons at bottom of panel
        float btnW2 = 160.0f, btnH2 = 48.0f;
        float bx = panelBaseX + (panelW - (btnW2 * 2.0f + 12.0f)) * 0.5f;
        float by = panelY + panelH - 56.0f;
        // YES button
        SDL_Color yesBg{220,80,60, 200}; SDL_Color yesBorder{160,40,40,200};
        SDL_Color noBg{60,140,200,200}; SDL_Color noBorder{30,90,160,200};
        // Apply pulse alpha to buttons
        double aMul = std::clamp(buttonGroupAlpha + buttonFlash * buttonFlashAmount, 0.0, 1.0);
        yesBg.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(yesBg.a)));
        yesBorder.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(yesBorder.a)));
        noBg.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(noBg.a)));
        noBorder.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(noBorder.a)));

        UIRenderer::drawButton(renderer, ctx.pixelFont, bx + btnW2*0.5f, by, btnW2, btnH2, "YES", false, exitSelectedButton == 0, yesBg, yesBorder, true, nullptr);
        UIRenderer::drawButton(renderer, ctx.pixelFont, bx + btnW2*1.5f + 12.0f, by, btnW2, btnH2, "NO", false, exitSelectedButton == 1, noBg, noBorder, true, nullptr);

        // Ensure ctx mirrors selection for any other code
        if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
    }

    // Popups (settings only - level popup is now a separate state)
    if (ctx.showSettingsPopup && *ctx.showSettingsPopup) {
        bool musicOn = ctx.musicEnabled ? *ctx.musicEnabled : true;
        bool soundOn = SoundEffectManager::instance().isEnabled();
        UIRenderer::drawSettingsPopup(renderer, ctx.font, LOGICAL_W, LOGICAL_H, musicOn, soundOn);
    }

    // Draw animated options panel if in use (either animating or visible)
    if (optionsTransition > 0.0) {
        // HUD-style overlay: no opaque background; draw labels/values directly with separators
        const float panelW = 520.0f;
        const float panelH = 420.0f;
        float panelBaseX = (LOGICAL_W - panelW) * 0.5f + contentOffsetX;
        // Move the HUD higher by ~10% of logical height so it sits above center
        float panelBaseY = (LOGICAL_H - panelH) * 0.5f + contentOffsetY - (LOGICAL_H * 0.10f);
        float panelY = panelBaseY + (1.0f - eased) * moveAmount;

        // For options/settings we prefer the secondary (Exo2) font for longer descriptions.
        FontAtlas* retroFont = ctx.font ? ctx.font : ctx.pixelFont;

        if (retroFont) {
            retroFont->draw(renderer, panelBaseX + 12.0f, panelY + 6.0f, "OPTIONS", 1.8f, SDL_Color{120, 220, 255, 255});
        }

        SDL_FRect area{panelBaseX, panelY + 48.0f, panelW, panelH - 64.0f};
        constexpr int rowCount = 5;
        const float rowHeight = 64.0f;
        const float spacing = 8.0f;

        auto drawField = [&](int idx, float y, const std::string& label, const std::string& value) {
            SDL_FRect row{area.x, y, area.w, rowHeight};

            // Draw thin separator (1px high filled rect) so we avoid platform-specific line API differences
            SDL_SetRenderDrawColor(renderer, 60, 120, 160, 120);
            SDL_FRect sep{ row.x + 6.0f, row.y + row.h - 1.0f, row.w - 12.0f, 1.0f };
            SDL_RenderFillRect(renderer, &sep);

            // Highlight the selected row with a subtle outline
            if (idx == optionsSelectedRow) {
                SDL_SetRenderDrawColor(renderer, 80, 200, 255, 120);
                SDL_RenderRect(renderer, &row);
            }

                if (retroFont) {
                SDL_Color labelColor = SDL_Color{170, 210, 220, 255};
                SDL_Color valueColor = SDL_Color{160, 240, 255, 255};
                if (!label.empty()) {
                    float labelScale = 1.0f;
                    int labelW = 0, labelH = 0;
                    retroFont->measure(label, labelScale, labelW, labelH);
                    float labelY = row.y + (row.h - static_cast<float>(labelH)) * 0.5f;
                    retroFont->draw(renderer, row.x + 16.0f, labelY, label, labelScale, labelColor);
                }
                int valueW = 0, valueH = 0;
                float valueScale = 1.4f;
                retroFont->measure(value, valueScale, valueW, valueH);
                float valX = row.x + row.w - static_cast<float>(valueW) - 16.0f;
                float valY = row.y + (row.h - valueH) * 0.5f;
                retroFont->draw(renderer, valX, valY, value, valueScale, valueColor);
            }
        };

        float rowY = area.y + spacing;
        // FULLSCREEN
        bool isFS = ctx.fullscreenFlag ? *ctx.fullscreenFlag : Settings::instance().isFullscreen();
        drawField(0, rowY, "FULLSCREEN", isFS ? "ON" : "OFF");
        rowY += rowHeight + spacing;
        // MUSIC
        bool musicOn = ctx.musicEnabled ? *ctx.musicEnabled : Settings::instance().isMusicEnabled();
        drawField(1, rowY, "MUSIC", musicOn ? "ON" : "OFF");
        rowY += rowHeight + spacing;
        // SOUND FX
        bool soundOn = SoundEffectManager::instance().isEnabled();
        drawField(2, rowY, "SOUND FX", soundOn ? "ON" : "OFF");
        rowY += rowHeight + spacing;
        // SMOOTH SCROLL
        bool smooth = Settings::instance().isSmoothScrollEnabled();
        drawField(3, rowY, "SMOOTH SCROLL", smooth ? "ON" : "OFF");
        rowY += rowHeight + spacing;
        // RETURN TO MENU
        drawField(4, rowY, "", "RETURN TO MENU");
    }

    // Draw inline help HUD (no boxed background) — match Options/Exit style
    if (helpTransition > 0.0) {
        float easedH = static_cast<float>(helpTransition);
        easedH = easedH * easedH * (3.0f - 2.0f * easedH);
        const float PW = std::min(520.0f, LOGICAL_W * 0.65f);
        const float PH = std::min(420.0f, LOGICAL_H * 0.72f);
        float panelBaseX = (LOGICAL_W - PW) * 0.5f + contentOffsetX;
        float panelBaseY = (LOGICAL_H - PH) * 0.5f + contentOffsetY - (LOGICAL_H * 0.10f);
        float slideAmount = LOGICAL_H * 0.42f;
        float panelY = panelBaseY + (1.0f - easedH) * slideAmount;

        FontAtlas* f = ctx.pixelFont ? ctx.pixelFont : ctx.font;
        if (f) {
            // Header (smaller)
            f->draw(renderer, panelBaseX + 12.0f, panelY + 6.0f, "HELP & SHORTCUTS", 1.25f, SDL_Color{255,220,0,255});

            // Content layout (two columns)
            const float contentPadding = 16.0f;
            const float columnGap = 18.0f;
            const float columnWidth = (PW - contentPadding * 2.0f - columnGap) * 0.5f;
            const float leftX = panelBaseX + contentPadding;
            const float rightX = leftX + columnWidth + columnGap;

            // Shortcut entries (copied from HelpOverlay)
            struct ShortcutEntry { const char* combo; const char* description; };
            const ShortcutEntry generalShortcuts[] = {
                {"F1", "Toggle this help overlay"},
                {"ESC", "Back / cancel current popup"},
                {"F11 or ALT+ENTER", "Toggle fullscreen"},
                {"M", "Mute or unmute music"},
                {"K", "Toggle sound effects"}
            };
            const ShortcutEntry menuShortcuts[] = {
                {"ARROW KEYS", "Navigate menu buttons"},
                {"ENTER / SPACE", "Activate highlighted action"}
            };
            const ShortcutEntry gameplayShortcuts[] = {
                {"LEFT / RIGHT", "Move active piece"},
                {"DOWN", "Soft drop (faster fall)"},
                {"SPACE", "Hard drop / instant lock"},
                {"UP", "Rotate clockwise"},
                {"H", "Hold / swap current piece"},
                {"X", "Toggle rotation direction used by UP"},
                {"P", "Pause or resume"},
                {"ESC", "Open exit confirmation"}
            };

            // Helper to draw text with extra letter-spacing (tracking)
            auto drawSpaced = [&](float sx, float sy, const char* text, float scale, SDL_Color color, float extraPx) {
                std::string stext(text);
                float x = sx;
                for (size_t i = 0; i < stext.size(); ++i) {
                    std::string ch(1, stext[i]);
                    f->draw(renderer, x, sy, ch.c_str(), scale, color);
                    int cw = 0, chh = 0;
                    f->measure(ch.c_str(), scale, cw, chh);
                    x += static_cast<float>(cw) + extraPx;
                }
            };

            auto drawSection = [&](float sx, float& cursorY, const char* title, const ShortcutEntry* entries, int count) {
                // Section title (smaller) with added letter spacing (reduced scale)
                drawSpaced(sx, cursorY, title, 0.85f, SDL_Color{180,200,255,255}, 4.0f);
                // Add extra gap after the headline so it separates clearly from the first row
                cursorY += 28.0f;
                for (int i = 0; i < count; ++i) {
                    const auto &entry = entries[i];
                    // Combo/key label
                    f->draw(renderer, sx, cursorY, entry.combo, 0.70f, SDL_Color{255,255,255,255});
                    // Slightly more space between the combo/key and the description
                    cursorY += 26.0f;

                    // Description (smaller) with increased spacing
                    f->draw(renderer, sx + 6.0f, cursorY, entry.description, 0.62f, SDL_Color{200,210,230,255});
                    int w=0,h=0; f->measure(entry.description, 0.62f, w, h);
                    cursorY += static_cast<float>(h) + 16.0f;
                }

                // (rest of help render continues below)
                // Add a larger gap between sections
                cursorY += 22.0f;

        // Draw inline ABOUT HUD (no boxed background) — simple main info
        if (aboutTransition > 0.0) {
            float easedA = static_cast<float>(aboutTransition);
            easedA = easedA * easedA * (3.0f - 2.0f * easedA);
            const float PW = std::min(520.0f, LOGICAL_W * 0.65f);
            const float PH = std::min(320.0f, LOGICAL_H * 0.60f);
            float panelBaseX = (LOGICAL_W - PW) * 0.5f + contentOffsetX;
            float panelBaseY = (LOGICAL_H - PH) * 0.5f + contentOffsetY - (LOGICAL_H * 0.10f);
            float slideAmount = LOGICAL_H * 0.42f;
            float panelY = panelBaseY + (1.0f - easedA) * slideAmount;

            FontAtlas* f = ctx.pixelFont ? ctx.pixelFont : ctx.font;
            if (f) {
                f->draw(renderer, panelBaseX + 12.0f, panelY + 6.0f, "ABOUT", 1.25f, SDL_Color{255,220,0,255});

                float x = panelBaseX + 16.0f;
                float y = panelY + 52.0f;
                const float lineGap = 30.0f;
                const SDL_Color textCol{200, 210, 230, 255};
                const SDL_Color keyCol{255, 255, 255, 255};

                f->draw(renderer, x, y, "SDL3 SPACETRIS", 1.05f, keyCol); y += lineGap;
                f->draw(renderer, x, y, "C++20 / SDL3 / SDL3_ttf", 0.80f, textCol); y += lineGap + 6.0f;

                f->draw(renderer, x, y, "GAMEPLAY", 0.85f, SDL_Color{180,200,255,255}); y += lineGap;
                f->draw(renderer, x, y, "H     Hold / swap current piece", 0.78f, textCol); y += lineGap;
                f->draw(renderer, x, y, "SPACE Hard drop", 0.78f, textCol); y += lineGap;
                f->draw(renderer, x, y, "P     Pause", 0.78f, textCol); y += lineGap + 6.0f;

                f->draw(renderer, x, y, "UI", 0.85f, SDL_Color{180,200,255,255}); y += lineGap;
                f->draw(renderer, x, y, "F1    Toggle help overlay", 0.78f, textCol); y += lineGap;
                f->draw(renderer, x, y, "ESC   Back / exit prompt", 0.78f, textCol); y += lineGap + 10.0f;

                f->draw(renderer, x, y, "PRESS ESC OR ARROW KEYS TO RETURN", 0.75f, SDL_Color{215,220,240,255});
            }
        }
            };

            const float contentTopY = panelY + 30.0f;
            float leftCursor = contentTopY - static_cast<float>(helpScroll);
            float rightCursor = contentTopY - static_cast<float>(helpScroll);
            drawSection(leftX, leftCursor, "GENERAL", generalShortcuts, (int)(sizeof(generalShortcuts)/sizeof(generalShortcuts[0])));
            drawSection(leftX, leftCursor, "MENUS", menuShortcuts, (int)(sizeof(menuShortcuts)/sizeof(menuShortcuts[0])));
            drawSection(rightX, rightCursor, "GAMEPLAY", gameplayShortcuts, (int)(sizeof(gameplayShortcuts)/sizeof(gameplayShortcuts[0])));

            // Ensure helpScroll bounds (simple clamp)
            float contentHeight = std::max(leftCursor, rightCursor) - contentTopY;
            float maxScroll = std::max(0.0f, contentHeight - (PH - 120.0f));
            if (helpScroll < 0.0) helpScroll = 0.0;
            if (helpScroll > maxScroll) helpScroll = maxScroll;
        }
    }

    // Draw inline level selector HUD (no background) if active
    if (levelTransition > 0.0) {
        float easedL = static_cast<float>(levelTransition);
        easedL = easedL * easedL * (3.0f - 2.0f * easedL);
        const float PW = std::min(520.0f, LOGICAL_W * 0.65f);
        const float PH = std::min(360.0f, LOGICAL_H * 0.7f);
        float panelBaseX = (LOGICAL_W - PW) * 0.5f + contentOffsetX;
        float panelBaseY = (LOGICAL_H - PH) * 0.5f + contentOffsetY - (LOGICAL_H * 0.10f);
        float slideAmount = LOGICAL_H * 0.42f;
        float panelY = panelBaseY + (1.0f - easedL) * slideAmount;

        // Header
        FontAtlas* titleFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
        if (titleFont) titleFont->draw(renderer, panelBaseX + PW * 0.5f - 140.0f, panelY + 6.0f, "SELECT STARTING LEVEL", 1.1f, SDL_Color{160,220,255,255});

        // Grid area
        float marginX = 34.0f, marginY = 56.0f;
        SDL_FRect area{ panelBaseX + marginX, panelY + marginY, PW - 2.0f * marginX, PH - marginY - 28.0f };
        const int cols = 4, rows = 5;
        const float gapX = 12.0f, gapY = 12.0f;
        float cellW = (area.w - (cols - 1) * gapX) / cols;
        float cellH = (area.h - (rows - 1) * gapY) / rows;

        for (int i = 0; i < 20; ++i) {
            int r = i / cols, c = i % cols;
            SDL_FRect rc{ area.x + c * (cellW + gapX), area.y + r * (cellH + gapY), cellW, cellH };
            bool hovered = (levelSelected == i) || (levelHovered == i);
            bool selected = (ctx.startLevelSelection && *ctx.startLevelSelection == i);
            // Use the project's gold/yellow tone for selected level to match UI accents
            SDL_Color selectedFill = SDL_Color{255,204,0,160};
            SDL_Color fill = selected ? selectedFill : (hovered ? SDL_Color{60,80,100,120} : SDL_Color{30,40,60,110});
            SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
            SDL_SetRenderDrawColor(renderer, fill.r, fill.g, fill.b, fill.a);
            SDL_RenderFillRect(renderer, &rc);
            SDL_SetRenderDrawColor(renderer, 80,100,120,160);
            SDL_RenderRect(renderer, &rc);

            // Draw level number
            FontAtlas* f = ctx.font ? ctx.font : ctx.pixelFont;
            if (f) {
                char buf[8]; std::snprintf(buf, sizeof(buf), "%d", i);
                int w=0,h=0; f->measure(buf, 1.6f, w, h);
                f->draw(renderer, rc.x + (rc.w - w) * 0.5f, rc.y + (rc.h - h) * 0.5f, buf, 1.6f, SDL_Color{220,230,240,255});
            }
        }

        // Draw animated highlight (interpolated) on top of cells
        if (levelHighlightInitialized) {
            float hx = (float)levelHighlightX;
            float hy = (float)levelHighlightY;
            float hw = cellW + 6.0f;
            float hh = cellH + 6.0f;
            // Draw multi-layer glow: outer faint, mid, inner bright
            SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
            // Outer glow
            SDL_SetRenderDrawColor(renderer, levelHighlightColor.r, levelHighlightColor.g, levelHighlightColor.b, (Uint8)(levelHighlightGlowAlpha * 80));
            SDL_FRect outer{ hx - (hw * 0.5f + 8.0f), hy - (hh * 0.5f + 8.0f), hw + 16.0f, hh + 16.0f };
            SDL_RenderRect(renderer, &outer);
            // Mid glow
            SDL_SetRenderDrawColor(renderer, levelHighlightColor.r, levelHighlightColor.g, levelHighlightColor.b, (Uint8)(levelHighlightGlowAlpha * 140));
            SDL_FRect mid{ hx - (hw * 0.5f + 4.0f), hy - (hh * 0.5f + 4.0f), hw + 8.0f, hh + 8.0f };
            SDL_RenderRect(renderer, &mid);
            // Inner outline
            SDL_SetRenderDrawColor(renderer, levelHighlightColor.r, levelHighlightColor.g, levelHighlightColor.b, levelHighlightColor.a);
            SDL_FRect inner{ hx - hw * 0.5f, hy - hh * 0.5f, hw, hh };
            // Draw multiple rects to simulate thickness
            for (int t = 0; t < levelHighlightThickness; ++t) {
                SDL_FRect r{ inner.x - t, inner.y - t, inner.w + t * 2.0f, inner.h + t * 2.0f };
                SDL_RenderRect(renderer, &r);
            }
        }

        // Instructions
        FontAtlas* foot = ctx.font ? ctx.font : ctx.pixelFont;
        if (foot) foot->draw(renderer, panelBaseX + PW*0.5f - 160.0f, panelY + PH + 40.0f, "ARROWS = NAV  •  ENTER = SELECT  •  ESC = CANCEL", 0.9f, SDL_Color{160,180,200,200});
    }
    // Trace exit
    {
        FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render exit\n"); fclose(f); }
    }

    // Network coop flow polling (non-blocking)
    if (coopSetupAnimating || coopSetupVisible) {
        if (coopSetupStep == CoopSetupStep::NetworkWaiting && coopNetworkSession) {
            coopNetworkSession->poll(0);

            // Update status depending on connection and role.
            if (!coopNetworkSession->isConnected()) {
                // Keep existing text (WAITING/CONNECTING) unless an error occurs.
            } else {
                // Host sends handshake after peer connects.
                if (coopNetworkRoleSelected == 0 && !coopNetworkHandshakeSent) {
                    std::random_device rd;
                    uint32_t seed = static_cast<uint32_t>(rd());
                    if (seed == 0u) seed = 1u;
                    const uint8_t startLevel = static_cast<uint8_t>(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
                    NetSession::Handshake hs{ seed, 0u, startLevel };
                    if (coopNetworkSession->sendHandshake(hs)) {
                        coopNetworkHandshakeSent = true;
                        ctx.coopNetRngSeed = seed;
                        coopNetworkStatusText = "CONNECTED";
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[NET COOP] HOST handshake sent seed=%u level=%u", seed, (unsigned)startLevel);
                    } else {
                        coopNetworkStatusText = "HANDSHAKE FAILED";
                    }
                }

                // Client waits for handshake.
                if (coopNetworkRoleSelected == 1) {
                    auto hs = coopNetworkSession->takeReceivedHandshake();
                    if (hs.has_value()) {
                        coopNetworkStatusText = "CONNECTED";
                        coopNetworkHandshakeSent = true;
                        ctx.coopNetRngSeed = hs->rngSeed;
                        if (ctx.startLevelSelection) {
                            *ctx.startLevelSelection = static_cast<int>(hs->startLevel);
                        }
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[NET COOP] CLIENT handshake recv seed=%u level=%u", hs->rngSeed, (unsigned)hs->startLevel);
                    } else {
                        coopNetworkStatusText = "CONNECTED - WAITING FOR HOST...";
                    }
                }

                // Confirmed connection => start COOPERATE (network) session.
                // Note: gameplay/network input injection is implemented separately.
                if (coopNetworkHandshakeSent) {
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
                                "[NET COOP] START gameplay (role=%s localIsLeft=%d seed=%u level=%u)",
                                (coopNetworkRoleSelected == 0 ? "HOST" : "CLIENT"),
                                (coopNetworkRoleSelected == 0 ? 1 : 0),
                                (unsigned)ctx.coopNetRngSeed,
                                (unsigned)(ctx.startLevelSelection ? *ctx.startLevelSelection : 0));
                    // Hand off the session to gameplay.
                    if (ctx.coopNetSession) {
                        ctx.coopNetSession->shutdown();
                        ctx.coopNetSession.reset();
                    }

                    ctx.coopNetEnabled = true;
                    ctx.coopNetIsHost = (coopNetworkRoleSelected == 0);
                    ctx.coopNetLocalIsLeft = (coopNetworkRoleSelected == 0);
                    ctx.coopNetTick = 0;
                    ctx.coopNetPendingButtons = 0;
                    ctx.coopNetDesyncDetected = false;

                    const uint32_t seed = (ctx.coopNetRngSeed == 0u) ? 1u : ctx.coopNetRngSeed;
                    const uint8_t startLevel = static_cast<uint8_t>(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);

                    if (ctx.coopVsAI) {
                        *ctx.coopVsAI = false;
                    }
                    if (ctx.game) {
                        ctx.game->setMode(GameMode::Cooperate);
                        ctx.game->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
                    }
                    if (ctx.coopGame) {
                        // Deterministic reset for network coop.
                        ctx.coopGame->resetDeterministic(startLevel, seed);
                    }

                    // Transfer ownership of the active session.
                    ctx.coopNetSession = std::move(coopNetworkSession);

                    // Close the panel without restarting menu music; gameplay will take over.
                    showCoopSetupPanel(false, false);

                    // For network lockstep, do NOT run the menu->play countdown/fade.
                    // Any local countdown introduces drift and stalls.
                    if (ctx.gameplayCountdownActive) *ctx.gameplayCountdownActive = false;
                    if (ctx.menuPlayCountdownArmed) *ctx.menuPlayCountdownArmed = false;
                    if (ctx.game) ctx.game->setPaused(false);

                    if (ctx.stateManager) {
                        ctx.stateManager->setState(AppState::Playing);
                    } else if (ctx.startPlayTransition) {
                        // Fallback if state manager is unavailable.
                        ctx.startPlayTransition();
                    }
                }
            }
        }
    }
}


