// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// app_wizard.cpp — First-run setup wizard
// Split from app.cpp for maintainability.

#include "app.h"
#include "rpc/rpc_client.h"
#include "rpc/rpc_worker.h"
#include "rpc/connection.h"
#include "config/settings.h"
#include "daemon/daemon_controller.h"
#include "daemon/embedded_daemon.h"
#include "ui/notifications.h"
#include "ui/material/color_theme.h"
#include "ui/material/type.h"
#include "ui/material/typography.h"
#include "ui/material/draw_helpers.h"
#include "ui/schema/ui_schema.h"
#include "ui/schema/skin_manager.h"
#include "ui/effects/low_spec.h"
#include "ui/windows/balance_tab.h"
#include "util/platform.h"
#include "util/bootstrap.h"
#include "util/secure_vault.h"
#include "util/i18n.h"
#include "util/perf_log.h"
#include "embedded/IconsMaterialDesign.h"
#include "resources/embedded_resources.h"

#include "imgui.h"
#include <nlohmann/json.hpp>
#include <cstring>
#include <filesystem>
#include <thread>
#include <chrono>

namespace dragonx {

using json = nlohmann::json;

namespace {

struct WizardLowSpecSnapshot {
    bool valid = false;
    float blur = 0.0f;
    float uiOp = 0.0f;
    bool fx = false;
    bool scanline = false;
};

struct WizardUiState {
    float blur_amount = 1.5f;
    bool theme_effects = true;
    float ui_opacity = 1.0f;
    bool low_spec = false;
    bool scanline = true;
    std::string balance_layout = "classic";
    int language_index = 0;
    bool appearance_init = false;
    WizardLowSpecSnapshot low_spec_snapshot;
    float card0_max_h = 0.0f;
    float card1_max_h = 0.0f;
    double external_last_check = -10.0;
    bool daemon_prestarted = false;
};

WizardUiState s_wizardUi;

} // namespace

void App::restartWizard()
{
    if (!supportsFullNodeLifecycleActions()) {
        ui::Notifications::instance().warning("Lite wallet lifecycle requests are available from Settings as dry-run readiness checks");
        return;
    }

    DEBUG_LOGF("[App] Restarting setup wizard — stopping daemon...\n");

    // Reset crash counter for fresh wizard attempt
    if (daemon_controller_) {
        daemon_controller_->resetCrashCount();
    }

    // Disconnect RPC
    if (rpc_ && rpc_->isConnected()) {
        rpc_->disconnect();
    }
    onDisconnected("Wizard restart");

    // Stop the embedded daemon in a background thread to avoid
    // blocking the UI for up to 32 seconds (RPC stop + process wait).
    if (daemon_controller_ && isEmbeddedDaemonRunning()) {
        async_tasks_.submit("wizard-restart-stop-daemon", [this](const util::AsyncTaskManager::Token& token) {
            if (token.cancelled()) return;
            stopEmbeddedDaemon();
        });
    }

    // Enter wizard — the wizard completion handler already calls
    // startEmbeddedDaemon() + tryConnect(), so no extra logic needed.
    wizard_phase_ = WizardPhase::Appearance;
}


// ===========================================================================
// First-Run Wizard Rendering
// ===========================================================================

void App::renderFirstRunWizard() {
    auto& wizardUi = s_wizardUi;
    ImGuiViewport* viewport = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos(viewport->WorkPos);
    ImGui::SetNextWindowSize(viewport->WorkSize);

    ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
                             ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
                             ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoSavedSettings |
                             ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
    ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));   // reveal the skin backdrop behind
    ImGui::Begin("##FirstRunWizard", nullptr, flags);
    ImGui::PopStyleColor();
    ImGui::PopStyleVar();

    ImDrawList* dl = ImGui::GetWindowDrawList();
    ImVec2 winPos = ImGui::GetWindowPos();
    ImVec2 winSize = ImGui::GetWindowSize();

    // The app's skin backdrop (marble / gradient / acrylic) is already painted behind every window by
    // drawWindowBackdrop(); reveal it here instead of a flat Surface() slab, under a gentle theme-tinted
    // scrim so the wizard cards and text keep their contrast on busy skins.
    ImU32 bgCol = ui::material::Surface();   // still used by the completed / not-reached card overlays
    dl->AddRectFilled(winPos, ImVec2(winPos.x + winSize.x, winPos.y + winSize.y),
                      ui::material::WithAlpha(ui::material::Background(), 120));

    // --- Determine which of the 3 masonry sections is focused ---
    // 0 = Appearance, 1 = Bootstrap, 2 = Encrypt + PIN
    int focusIdx = 0;
    switch (wizard_phase_) {
    case WizardPhase::Appearance: focusIdx = 0; break;
    case WizardPhase::BootstrapOffer:
    case WizardPhase::BootstrapInProgress:
    case WizardPhase::BootstrapFailed:
        focusIdx = 1; break;
    case WizardPhase::EncryptOffer:
    case WizardPhase::EncryptInProgress:
    case WizardPhase::PinSetup:
        focusIdx = 2; break;
    default: focusIdx = 0; break;
    }

    // Card visual state: 0 = not-reached, 1 = focused, 2 = completed
    auto cardState = [&](int idx) -> int {
        if (idx < focusIdx) return 2;
        if (idx == focusIdx) return 1;
        return 0;
    };

    // --- Fonts & Colors ---
    const auto& S = ui::schema::UI();
    ImFont* titleFont = S.resolveFont(S.label("screens.first-run", "title").font);
    if (!titleFont) titleFont = ui::material::Type().h5();
    ImFont* bodyFont = S.resolveFont(S.label("screens.first-run", "subtitle").font);
    if (!bodyFont) bodyFont = ui::material::Type().body1();
    ImFont* captionFont = S.resolveFont(S.label("screens.first-run", "trust-warning").font);
    if (!captionFont) captionFont = ui::material::Type().caption();

    ImU32 textCol = ui::material::OnSurface();
    ImU32 dimCol = (textCol & 0x00FFFFFF) | (IM_COL32_A_MASK & IM_COL32(0,0,0,180));
    ImFont* iconFont = ui::material::Type().iconSmall();
    if (!iconFont) iconFont = captionFont;

    // DPI scale factor — multiply all pixel constants by dp
    const float dp = ui::Layout::dpiScale();

    // Vertical scroll: the wizard cards are hand-drawn at absolute Y offsets and grow ~1.5x with the
    // font-scale setting, so at high scale the focused card's primary button (Continue / Encrypt & Continue
    // / Skip) can fall below the fixed window. Offset the whole layout by a wheel-driven scroll, clamped to
    // last frame's measured content height, so every control stays reachable. The window keeps
    // NoScrollWithMouse, so ImGui doesn't consume the wheel — we read the raw delta and apply our own offset.
    static float s_wizScroll = 0.0f, s_wizContentH = 0.0f;
    if (ImGui::IsWindowAppearing()) s_wizScroll = 0.0f;
    const float wizMaxScroll = std::max(0.0f, s_wizContentH - winSize.y);
    // Don't steal the wheel from an open combo popup (e.g. the 9-item Language dropdown, which is a
    // scrollable popup): NoPopupHierarchy stops the popup counting as hovering the wizard, and the
    // IsPopupOpen guard ensures no wheel is consumed for the whole wizard while any popup is showing.
    const bool wizPopupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);
    if (wizMaxScroll > 0.0f && !wizPopupOpen &&
        ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) {
        float wheel = ImGui::GetIO().MouseWheel;
        if (wheel != 0.0f) s_wizScroll -= wheel * 60.0f * dp;
    }
    s_wizScroll = std::max(0.0f, std::min(s_wizScroll, wizMaxScroll));
    const float scrollY = s_wizScroll;

    // --- Header: Logo + Welcome ---
    // Vertically center the content when it fits (mirrors the horizontal centering below): on a tall
    // monitor top-anchoring leaves a large void under the cards. Using last frame's measured block
    // height, when the content fits inside the window (and we're NOT overflowing, so this doesn't
    // fight the scroll), push everything down by half the leftover space. No-op once content
    // fills/exceeds the window (s_wizContentH >= winSize.y ⇒ wizMaxScroll > 0 ⇒ vCenter skipped).
    float vCenter = 0.0f;
    if (wizMaxScroll == 0.0f && s_wizContentH > 0.0f && s_wizContentH < winSize.y)
        vCenter = std::max(0.0f, (winSize.y - s_wizContentH) * 0.5f);
    float headerCy = winPos.y - scrollY + 20.0f * dp + vCenter;
    float logoSize = S.drawElement("screens.first-run", "logo").sizeOr(56.0f);
    if (logo_tex_ != 0) {
        float aspect = (logo_h_ > 0) ? (float)logo_w_ / (float)logo_h_ : 1.0f;
        float logoW = logoSize * aspect;
        float logoX = winPos.x + (winSize.x - logoW) * 0.5f;
        dl->AddImage(logo_tex_, ImVec2(logoX, headerCy), ImVec2(logoX + logoW, headerCy + logoSize));
    }
    headerCy += logoSize + 8.0f * dp;

    {
        const char* welcomeTitle = TR("wiz_welcome_title");
        ImVec2 wts = titleFont->CalcTextSizeA(titleFont->LegacySize, FLT_MAX, 0, welcomeTitle);
        dl->AddText(titleFont, titleFont->LegacySize,
                    ImVec2(winPos.x + (winSize.x - wts.x) * 0.5f, headerCy), textCol, welcomeTitle);
        headerCy += wts.y + 6.0f * dp;

        // Warmer, less-sparse header: a one-line subtitle under the welcome (dimmed body).
        const char* welcomeSub = TR("wiz_welcome_sub");
        ImVec2 sts = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, 0, welcomeSub);
        dl->AddText(bodyFont, bodyFont->LegacySize,
                    ImVec2(winPos.x + (winSize.x - sts.x) * 0.5f, headerCy), dimCol, welcomeSub);
        headerCy += sts.y + 16.0f * dp;
    }

    // --- Masonry: 2 columns ---
    // Left column:  Card 0 (Appearance) on top, Card 2 (Encrypt+PIN) below
    // Right column: Card 1 (Bootstrap)
    float totalW = std::min(920.0f * dp, winSize.x - 40.0f * dp);
    float gap = 16.0f * dp;
    float colW = (totalW - gap) * 0.5f;
    float areaX = winPos.x + (winSize.x - totalW) * 0.5f;
    float leftX = areaX;
    float rightX = areaX + colW + gap;
    float cardPad = 24.0f * dp;
    float cardRound = 12.0f * dp;
    float topY = headerCy;

    // Step icon helper
    auto stepIcon = [](int state) -> const char* {
        return (state == 2) ? ICON_MD_CHECK_CIRCLE :
               (state == 1) ? ICON_MD_RADIO_BUTTON_CHECKED :
                              ICON_MD_RADIO_BUTTON_UNCHECKED;
    };

    // Split draw list: 0 = backgrounds, 1 = content, 2 = overlays/borders
    dl->ChannelsSplit(3);
    dl->ChannelsSetCurrent(1);

    // Helper: finalize card — draw background, accent border or dim overlay
    auto finalizeCard = [&](float cardX_, float cardW_, float ytop, float ybot, int state) {
        ImVec2 cMin(cardX_, ytop);
        ImVec2 cMax(cardX_ + cardW_, ybot);

        // Background (channel 0)
        dl->ChannelsSetCurrent(0);
        if (state == 1) {
            // Focused card lifts off the backdrop with the app's uniform card shadow (not a hard offset).
            ui::material::DrawCardDropShadow(dl, cMin, cMax, cardRound);
        }
        // Use DrawGlassPanel for proper acrylic/opacity/noise/theme effects
        ui::material::GlassPanelSpec glass;
        glass.rounding = cardRound;
        ui::material::DrawGlassPanel(dl, cMin, cMax, glass);

        // Overlays & borders (channel 2)
        dl->ChannelsSetCurrent(2);
        if (state == 1) {
            // Focused: soft accent ring
            dl->AddRect(cMin, cMax, ui::material::Primary(), cardRound, 0, 1.5f * dp);
        } else if (state == 2) {
            // Completed: a light veil — reads as "done", not disabled.
            dl->AddRectFilled(cMin, cMax, (bgCol & 0x00FFFFFF) | IM_COL32(0, 0, 0, 70), cardRound);
        } else {
            // Upcoming: a gentle veil — reads as "waiting", not greyed-out.
            dl->AddRectFilled(cMin, cMax, (bgCol & 0x00FFFFFF) | IM_COL32(0, 0, 0, 115), cardRound);
        }

        dl->ChannelsSetCurrent(1);
    };


    // ======================= CARD 0: Appearance =======================
    float card0Top = topY;
    float card0Bot;
    {
        int state = cardState(0);
        bool isFocused = (state == 1);
        bool isCollapsed = (state == 2);  // Completed: minimize to a compact pill (mirrors Card 1)
        float cx = leftX + cardPad;
        float cy = card0Top + cardPad;
        float contentW = colW - 2 * cardPad;

        // Step indicator + title (inline when collapsed)
        if (isCollapsed) {
            // Compact single-line: check icon + "Step 1" + "Appearance"
            float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
            dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
            float labelX = cx + iconW + 4.0f * dp;
            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(labelX, cy), dimCol, TR("wiz_step1"));
            float step1W = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, TR("wiz_step1")).x;
            float titleX = labelX + step1W + 12.0f * dp;
            dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(titleX, cy), dimCol, TR("wiz_appearance"));
            cy += captionFont->LegacySize + 4.0f * dp;
        } else {
            // Step indicator
            {
                float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
                dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
                dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, TR("wiz_step1"));
                cy += captionFont->LegacySize + 6.0f * dp;
            }

            // Title
            {
                const char* t = TR("wiz_appearance");
                dl->AddText(titleFont, titleFont->LegacySize, ImVec2(cx, cy), textCol, t);
                cy += titleFont->LegacySize + 10.0f * dp;
            }

            // Separator
            dl->AddLine(ImVec2(cx, cy), ImVec2(cx + contentW, cy),
                        (textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp);
            cy += 14.0f * dp;
        }

        float& wiz_blur_amount = wizardUi.blur_amount;
        bool& wiz_theme_effects = wizardUi.theme_effects;
        float& wiz_ui_opacity = wizardUi.ui_opacity;
        bool& wiz_low_spec = wizardUi.low_spec;
        bool& wiz_scanline = wizardUi.scanline;
        std::string& wiz_balance_layout = wizardUi.balance_layout;
        int& wiz_language_index = wizardUi.language_index;
        bool& wiz_appearance_init = wizardUi.appearance_init;
        if (!wiz_appearance_init) {
            wiz_blur_amount = settings_->getBlurMultiplier();
            wiz_theme_effects = settings_->getThemeEffectsEnabled();
            wiz_ui_opacity = settings_->getUIOpacity();
            wiz_low_spec = settings_->getLowSpecMode();
            wiz_scanline = settings_->getScanlineEnabled();
            wiz_balance_layout = settings_->getBalanceLayout();
            // Find current language index
            const auto& wiz_languages = util::I18n::instance().getAvailableLanguages();
            std::string wiz_cur_lang = settings_->getLanguage();
            if (wiz_cur_lang.empty()) wiz_cur_lang = "en";
            int idx = 0;
            for (const auto& lang : wiz_languages) {
                if (lang.first == wiz_cur_lang) { wiz_language_index = idx; break; }
                idx++;
            }
            // Apply loaded settings to runtime so visuals match slider values
            ui::effects::setLowSpecMode(wiz_low_spec);
            ui::effects::ImGuiAcrylic::ApplyBlurAmount(wiz_blur_amount);
            ui::effects::ImGuiAcrylic::SetUIOpacity(wiz_ui_opacity);
            ui::effects::ThemeEffects::instance().setEnabled(wiz_theme_effects);
            ui::effects::ThemeEffects::instance().setReducedTransparency(!wiz_theme_effects);
            ui::ConsoleTab::s_scanline_enabled = wiz_scanline;
            wiz_appearance_init = true;
        }

        // Controls: rendered for the focused and upcoming states so content is visible under
        // the dim overlay; skipped entirely once completed so the card shrinks to a compact pill.
        if (!isCollapsed) {
        // Render controls always so content is visible under the dim
        // overlay when not focused; disable interaction when not active.
        ImGui::BeginDisabled(!isFocused);

        // --- Theme combo ---
        {
            auto& skinMgr = ui::schema::SkinManager::instance();
            const auto& skins = skinMgr.available();
            std::string activePreview = "DragonX";
            for (const auto& skin : skins) {
                if (skin.id == skinMgr.activeSkinId()) { activePreview = skin.name; break; }
            }
            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, TR("theme"));
            float comboX = cx + 110.0f * dp;
            float comboW = contentW - 110.0f * dp;
            ImGui::SetCursorScreenPos(ImVec2(comboX, cy));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
            ImGui::SetNextItemWidth(comboW);
            if (ImGui::BeginCombo("##wiz_theme", activePreview.c_str())) {
                ImGui::TextDisabled(TR("wiz_theme_builtin"));
                ImGui::Separator();
                for (const auto& skin : skins) {
                    if (!skin.bundled) continue;
                    bool sel = (skin.id == skinMgr.activeSkinId());
                    if (ImGui::Selectable(skin.name.c_str(), sel)) {
                        skinMgr.setActiveSkin(skin.id);
                        settings_->setSkinId(skin.id);
                        settings_->save();
                    }
                    if (sel) ImGui::SetItemDefaultFocus();
                }
                bool hasCustom = false;
                for (const auto& skin : skins) { if (!skin.bundled) { hasCustom = true; break; } }
                if (hasCustom) {
                    ImGui::Spacing();
                    ImGui::TextDisabled(TR("wiz_theme_custom"));
                    ImGui::Separator();
                    for (const auto& skin : skins) {
                        if (skin.bundled) continue;
                        bool sel = (skin.id == skinMgr.activeSkinId());
                        if (!skin.valid) {
                            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0.3f,0.3f,1));
                            ImGui::BeginDisabled(true);
                            ImGui::Selectable((skin.name + TR("wiz_theme_invalid")).c_str(), false);
                            ImGui::EndDisabled();
                            ImGui::PopStyleColor();
                        } else {
                            std::string lbl = skin.name;
                            if (!skin.author.empty()) lbl += "  (" + skin.author + ")";
                            if (ImGui::Selectable(lbl.c_str(), sel)) {
                                skinMgr.setActiveSkin(skin.id);
                                settings_->setSkinId(skin.id);
                                settings_->save();
                            }
                            if (sel) ImGui::SetItemDefaultFocus();
                        }
                    }
                }
                ImGui::EndCombo();
            }
            ImGui::PopStyleVar();
            cy += bodyFont->LegacySize + 16.0f * dp;
        }

        // --- Balance Layout combo ---
        {
            const auto& layouts = ui::GetBalanceLayouts();
            std::string balPreview = wiz_balance_layout;
            for (const auto& l : layouts) {
                if (l.id == wiz_balance_layout) { balPreview = l.name; break; }
            }
            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, TR("balance_layout"));
            float comboX = cx + 110.0f * dp;
            float comboW = contentW - 110.0f * dp;
            ImGui::SetCursorScreenPos(ImVec2(comboX, cy));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
            ImGui::SetNextItemWidth(comboW);
            if (ImGui::BeginCombo("##wiz_layout", balPreview.c_str())) {
                for (const auto& l : layouts) {
                    if (!l.enabled) continue;
                    bool sel = (l.id == wiz_balance_layout);
                    if (ImGui::Selectable(l.name.c_str(), sel)) {
                        wiz_balance_layout = l.id;
                        settings_->setBalanceLayout(wiz_balance_layout);
                        settings_->save();
                    }
                    if (sel) ImGui::SetItemDefaultFocus();
                }
                ImGui::EndCombo();
            }
            ImGui::PopStyleVar();
            cy += bodyFont->LegacySize + 16.0f * dp;
        }

        // --- Language combo ---
        {
            auto& i18n = util::I18n::instance();
            const auto& languages = i18n.getAvailableLanguages();
            std::vector<const char*> langNames;
            langNames.reserve(languages.size());
            for (const auto& lang : languages) langNames.push_back(lang.second.c_str());

            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, TR("language"));
            float comboX = cx + 110.0f * dp;
            float comboW = contentW - 110.0f * dp;
            ImGui::SetCursorScreenPos(ImVec2(comboX, cy));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
            ImGui::SetNextItemWidth(comboW);
            if (ImGui::Combo("##wiz_lang", &wiz_language_index, langNames.data(),
                             static_cast<int>(langNames.size()))) {
                auto it = languages.begin();
                std::advance(it, wiz_language_index);
                i18n.loadLanguage(it->first);
            }
            ImGui::PopStyleVar();
            cy += bodyFont->LegacySize + 20.0f * dp;
        }

        // --- Low-spec mode checkbox ---
        // Snapshot for restoring settings when low-spec is turned off
        WizardLowSpecSnapshot& wiz_lsSnap = wizardUi.low_spec_snapshot;

        ImGui::SetCursorScreenPos(ImVec2(cx, cy));
        ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
        if (ImGui::Checkbox("##wiz_lowspec", &wiz_low_spec)) {
            ui::effects::setLowSpecMode(wiz_low_spec);
            if (wiz_low_spec) {
                // Save current effect settings before zeroing
                wiz_lsSnap.valid = true;
                wiz_lsSnap.blur = wiz_blur_amount;
                wiz_lsSnap.uiOp = wiz_ui_opacity;
                wiz_lsSnap.fx = wiz_theme_effects;
                wiz_lsSnap.scanline = wiz_scanline;
                // Disable all heavy effects
                wiz_blur_amount = 0.0f;
                wiz_ui_opacity = 1.0f;
                wiz_theme_effects = false;
                wiz_scanline = false;
                ui::effects::ImGuiAcrylic::ApplyBlurAmount(0.0f);
                ui::effects::ImGuiAcrylic::SetUIOpacity(1.0f);
                settings_->setWindowOpacity(1.0f);
                ui::effects::ThemeEffects::instance().setEnabled(false);
                ui::effects::ThemeEffects::instance().setReducedTransparency(true);
                ui::ConsoleTab::s_scanline_enabled = false;
            } else if (wiz_lsSnap.valid) {
                // Restore previous effect settings
                wiz_blur_amount = wiz_lsSnap.blur;
                wiz_ui_opacity = wiz_lsSnap.uiOp;
                wiz_theme_effects = wiz_lsSnap.fx;
                wiz_scanline = wiz_lsSnap.scanline;
                ui::effects::ImGuiAcrylic::ApplyBlurAmount(wiz_blur_amount);
                ui::effects::ImGuiAcrylic::SetUIOpacity(wiz_ui_opacity);
                ui::effects::ThemeEffects::instance().setEnabled(wiz_theme_effects);
                ui::effects::ThemeEffects::instance().setReducedTransparency(false);
                ui::ConsoleTab::s_scanline_enabled = wiz_scanline;
                wiz_lsSnap.valid = false;
            }
            // Persist immediately so effects read correct values
            settings_->setAcrylicEnabled(wiz_blur_amount > 0.001f);
            settings_->setAcrylicQuality(wiz_blur_amount > 0.001f
                ? static_cast<int>(ui::effects::AcrylicQuality::Low)
                : static_cast<int>(ui::effects::AcrylicQuality::Off));
            settings_->setBlurMultiplier(wiz_blur_amount);
            settings_->setUIOpacity(wiz_ui_opacity);
            settings_->setThemeEffectsEnabled(wiz_theme_effects);
            settings_->setScanlineEnabled(wiz_scanline);
            settings_->setLowSpecMode(wiz_low_spec);
            settings_->save();
        }
        ImGui::PopStyleVar();
        ImGui::SameLine();
        dl->AddText(bodyFont, bodyFont->LegacySize,
                    ImVec2(ImGui::GetCursorScreenPos().x, cy + 2.0f * dp), textCol,
                    TR("low_spec_mode"));
        cy += bodyFont->LegacySize + 6.0f * dp;
        dl->AddText(captionFont, captionFont->LegacySize,
                    ImVec2(cx + 28.0f * dp, cy), dimCol, TR("wiz_lowspec_desc"));
        cy += captionFont->LegacySize + 16.0f * dp;

        ImGui::BeginDisabled(wiz_low_spec);

        // Acrylic blur slider
        dl->AddText(bodyFont, bodyFont->LegacySize,
                    ImVec2(cx, cy + 2.0f * dp), textCol,
                    TR("wiz_acrylic"));
        cy += bodyFont->LegacySize + 4.0f * dp;
        dl->AddText(captionFont, captionFont->LegacySize,
                    ImVec2(cx, cy), dimCol, TR("wiz_acrylic_desc"));
        cy += captionFont->LegacySize + 10.0f * dp;

        {
            dl->AddText(captionFont, captionFont->LegacySize,
                        ImVec2(cx + 4.0f * dp, cy), textCol, TR("wiz_level"));
            ImGui::SetCursorScreenPos(ImVec2(cx + 72.0f * dp, cy - 2.0f * dp));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
            float sliderW = contentW - 72.0f * dp;
            ImGui::SetNextItemWidth(std::max(80.0f * dp, sliderW));
            {
                char blur_fmt[16];
                if (wiz_blur_amount < 0.01f)
                    snprintf(blur_fmt, sizeof(blur_fmt), TR("wiz_off"));
                else
                    snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", wiz_blur_amount * 25.0f);
                if (ImGui::SliderFloat("##wiz_blur", &wiz_blur_amount, 0.0f, 4.0f, blur_fmt,
                                       ImGuiSliderFlags_AlwaysClamp)) {
                    if (wiz_blur_amount > 0.0f && wiz_blur_amount < 0.15f) wiz_blur_amount = 0.0f;
                    ui::effects::ImGuiAcrylic::ApplyBlurAmount(wiz_blur_amount);
                }
            }
            if (ImGui::IsItemDeactivatedAfterEdit()) {
                settings_->setAcrylicEnabled(wiz_blur_amount > 0.001f);
                settings_->setAcrylicQuality(wiz_blur_amount > 0.001f
                    ? static_cast<int>(ui::effects::AcrylicQuality::Low)
                    : static_cast<int>(ui::effects::AcrylicQuality::Off));
                settings_->setBlurMultiplier(wiz_blur_amount);
                settings_->save();
            }
            ImGui::PopStyleVar();
            cy += bodyFont->LegacySize + 16.0f * dp;
        }

        // Theme effects checkbox
        ImGui::SetCursorScreenPos(ImVec2(cx, cy));
        ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
        if (ImGui::Checkbox("##wiz_fx", &wiz_theme_effects)) {
            ui::effects::ThemeEffects::instance().setEnabled(wiz_theme_effects);
            ui::effects::ThemeEffects::instance().setReducedTransparency(!wiz_theme_effects);
            settings_->setThemeEffectsEnabled(wiz_theme_effects);
            settings_->save();
        }
        ImGui::PopStyleVar();
        ImGui::SameLine();
        dl->AddText(bodyFont, bodyFont->LegacySize,
                    ImVec2(ImGui::GetCursorScreenPos().x, cy + 2.0f * dp), textCol,
                    TR("wiz_theme_effects"));
        cy += bodyFont->LegacySize + 6.0f * dp;
        dl->AddText(captionFont, captionFont->LegacySize,
                    ImVec2(cx + 28.0f * dp, cy), dimCol, TR("wiz_theme_effects_desc"));
        cy += captionFont->LegacySize + 16.0f * dp;

        // UI Opacity slider
        dl->AddText(bodyFont, bodyFont->LegacySize,
                    ImVec2(cx, cy + 2.0f * dp), textCol,
                    TR("ui_opacity"));
        cy += bodyFont->LegacySize + 4.0f * dp;
        dl->AddText(captionFont, captionFont->LegacySize,
                    ImVec2(cx, cy), dimCol, TR("wiz_ui_opacity_desc"));
        cy += captionFont->LegacySize + 10.0f * dp;
        {
            ImGui::SetCursorScreenPos(ImVec2(cx, cy - 2.0f * dp));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
            ImGui::SetNextItemWidth(std::max(80.0f * dp, contentW));
            if (ImGui::SliderFloat("##wiz_ui_opacity", &wiz_ui_opacity, 0.3f, 1.0f, "%.2f")) {
                ui::effects::ImGuiAcrylic::SetUIOpacity(wiz_ui_opacity);
            }
            if (ImGui::IsItemDeactivatedAfterEdit()) {
                settings_->setUIOpacity(wiz_ui_opacity);
                settings_->save();
            }
            ImGui::PopStyleVar();
            cy += bodyFont->LegacySize + 16.0f * dp;
        }

        // Console scanline checkbox
        ImGui::SetCursorScreenPos(ImVec2(cx, cy));
        ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
        if (ImGui::Checkbox("##wiz_scanline", &wiz_scanline)) {
            ui::ConsoleTab::s_scanline_enabled = wiz_scanline;
            settings_->setScanlineEnabled(wiz_scanline);
            settings_->save();
        }
        ImGui::PopStyleVar();
        ImGui::SameLine();
        dl->AddText(bodyFont, bodyFont->LegacySize,
                    ImVec2(ImGui::GetCursorScreenPos().x, cy + 2.0f * dp), textCol,
                    TR("console_scanline"));
        cy += bodyFont->LegacySize + 6.0f * dp;
        dl->AddText(captionFont, captionFont->LegacySize,
                    ImVec2(cx + 28.0f * dp, cy), dimCol, TR("wiz_scanline_desc"));
        cy += captionFont->LegacySize + 24.0f * dp;

        ImGui::EndDisabled(); // low-spec

        ImGui::EndDisabled(); // !isFocused

        // Continue button (only when focused)
        if (isFocused) {
            float btnW = 140.0f * dp;
            float btnH = 40.0f * dp;
            float btnX = leftX + (colW - btnW) * 0.5f;

            ImGui::SetCursorScreenPos(ImVec2(btnX, cy));
            ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(ui::material::Primary()));
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
            ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
            if (ui::material::TactileButton(TR("wiz_continue"), ImVec2(btnW, btnH))) {
                // Save appearance choices, advance to Bootstrap
                settings_->setAcrylicEnabled(wiz_blur_amount > 0.001f);
                settings_->setAcrylicQuality(wiz_blur_amount > 0.001f
                    ? static_cast<int>(ui::effects::AcrylicQuality::Low)
                    : static_cast<int>(ui::effects::AcrylicQuality::Off));
                settings_->setBlurMultiplier(wiz_blur_amount);
                settings_->setThemeEffectsEnabled(wiz_theme_effects);
                settings_->setUIOpacity(wiz_ui_opacity);
                settings_->setLowSpecMode(wiz_low_spec);
                settings_->setScanlineEnabled(wiz_scanline);
                settings_->setBalanceLayout(wiz_balance_layout);
                settings_->save();
                wizard_phase_ = WizardPhase::BootstrapOffer;
            }
            ImGui::PopStyleVar();
            ImGui::PopStyleColor(3);
            cy += btnH;
        }

        } // if (!isCollapsed)

        cy += cardPad;
        // Lock card height to the tallest content ever seen (but not when collapsed)
        float& card0MaxH = wizardUi.card0_max_h;
        if (isCollapsed) {
            // Completed: finalize immediately as a compact pill (do not stretch to the
            // right column height, and skip the deferred stretch below).
            card0Bot = card0Top + (cy - card0Top);
            finalizeCard(leftX, colW, card0Top, card0Bot, state);
        } else {
            card0MaxH = std::max(card0MaxH, cy - card0Top);
            card0Bot = card0Top + card0MaxH;
            // Card 0 finalization deferred until after cards 1+2 are sized
        }
    }


    // ======================= CARD 1: Bootstrap =======================
    float card1Top = topY;
    float card1Bot;
    {
        int state = cardState(1);
        bool isFocused = (state == 1);
        bool isCollapsed = (state == 2 && cardState(2) == 1);  // Minimize when step 3 active
        float cx = rightX + cardPad;
        float cy = card1Top + cardPad;
        float contentW = colW - 2 * cardPad;

        // Step indicator + title (inline when collapsed)
        if (isCollapsed) {
            // Compact single-line: icon + "Step 2" + "Bootstrap" + checkmark
            float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
            dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
            float labelX = cx + iconW + 4.0f * dp;
            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(labelX, cy), dimCol, TR("wiz_step2"));
            float step2W = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, TR("wiz_step2")).x;
            float titleX = labelX + step2W + 12.0f * dp;
            dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(titleX, cy), dimCol, TR("wiz_bootstrap"));
            cy += captionFont->LegacySize + 4.0f * dp;
        } else {
            // Step indicator
            {
                float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
                dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
                dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, TR("wiz_step2"));
                cy += captionFont->LegacySize + 4.0f * dp;
            }

            // Title
            {
                const char* t = TR("wiz_bootstrap");
                dl->AddText(titleFont, titleFont->LegacySize, ImVec2(cx, cy), textCol, t);
                cy += titleFont->LegacySize + 6.0f * dp;
            }

            // Separator
            dl->AddLine(ImVec2(cx, cy), ImVec2(cx + contentW, cy),
                        (textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp);
            cy += 10.0f * dp;
        }

        // --- Content varies by sub-state (only when focused, skip when collapsed) ---
        if (isCollapsed) {
            // No content — card is minimized
        } else if (isFocused && wizard_phase_ == WizardPhase::BootstrapInProgress) {
            // ---- Bootstrap download in progress ----
            if (!bootstrap_) {
                wizard_phase_ = WizardPhase::EncryptOffer;
            } else {
                auto prog = bootstrap_->getProgress();

                const char* statusTitle;
                if (prog.state == util::Bootstrap::State::Downloading)
                    statusTitle = TR("bootstrap_downloading");
                else if (prog.state == util::Bootstrap::State::Verifying)
                    statusTitle = TR("bootstrap_verifying");
                else
                    statusTitle = TR("bootstrap_extracting");
                dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), textCol, statusTitle);
                cy += bodyFont->LegacySize + 12.0f * dp;

                // Progress bar
                float barH = 8.0f * dp, barR = 4.0f * dp;
                dl->AddRectFilled(ImVec2(cx, cy), ImVec2(cx + contentW, cy + barH),
                                  IM_COL32(255,255,255,30), barR);
                float fillW = contentW * (prog.percent / 100.0f);
                if (fillW > 0) {
                    dl->AddRectFilled(ImVec2(cx, cy), ImVec2(cx + fillW, cy + barH),
                                      ui::material::Primary(), barR);
                }
                cy += barH + 8.0f * dp;

                // Status text + percent
                {
                    char pctText[64];
                    snprintf(pctText, sizeof(pctText), "%.1f%%", prog.percent);
                    ImVec2 pts = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, 0, pctText);
                    dl->AddText(bodyFont, bodyFont->LegacySize,
                                ImVec2(cx + contentW - pts.x, cy), textCol, pctText);
                }
                dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol,
                            prog.status_text.c_str());
                cy += bodyFont->LegacySize + 6.0f * dp;

                if (prog.state == util::Bootstrap::State::Extracting) {
                    dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
                                dimCol, TR("bootstrap_wallet_protected"));
                    cy += captionFont->LegacySize + 6.0f * dp;
                }

                // Daemon status indicator
                {
                    bool daemonUp = isEmbeddedDaemonRunning();
                    const std::string& dStatus = getDaemonStatus();
                    ImU32 dotCol = daemonUp ? IM_COL32(76, 175, 80, 200)   // green
                                            : IM_COL32(120, 120, 120, 160); // gray
                    if (dStatus.find("Stopping") != std::string::npos)
                        dotCol = IM_COL32(255, 167, 38, 200); // orange
                    float dotR = 3.5f * dp;
                    dl->AddCircleFilled(ImVec2(cx + dotR, cy + captionFont->LegacySize * 0.5f),
                                        dotR, dotCol);
                    const char* label = daemonUp ? (dStatus.find("Stopping") != std::string::npos
                                                   ? TR("bootstrap_daemon_stopping")
                                                   : TR("bootstrap_daemon_running"))
                                                : TR("bootstrap_daemon_stopped");
                    dl->AddText(captionFont, captionFont->LegacySize,
                                ImVec2(cx + dotR * 2.0f + 6.0f * dp, cy),
                                (dimCol & 0x00FFFFFF) | IM_COL32(0,0,0,140), label);
                    cy += captionFont->LegacySize + 6.0f * dp;
                }

                cy += 12.0f * dp;

                // Cancel button
                float cancelW = 100.0f * dp;
                float cancelH = 36.0f * dp;
                float cancelBX = rightX + (colW - cancelW) * 0.5f;
                ImGui::SetCursorScreenPos(ImVec2(cancelBX, cy));
                ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
                if (ui::material::TactileButton(TR("cancel"), ImVec2(cancelW, cancelH))) {
                    bootstrap_->cancel();
                }
                ImGui::PopStyleVar();
                cy += cancelH;

                // Check completion
                if (bootstrap_->isDone()) {
                    auto finalProg = bootstrap_->getProgress();
                    if (finalProg.state == util::Bootstrap::State::Completed) {
                        bootstrap_.reset();
                        // Reconcile the preserved wallet.dat against the new chain once the daemon is up.
                        markPostBootstrapRescanPending();
                        wizard_phase_ = WizardPhase::EncryptOffer;
                    } else {
                        wizard_phase_ = WizardPhase::BootstrapFailed;
                    }
                }
            }

        } else if (isFocused && wizard_phase_ == WizardPhase::BootstrapFailed) {
            // ---- Bootstrap failed ----
            std::string errMsg;
            if (bootstrap_) {
                errMsg = bootstrap_->getProgress().error;
                bootstrap_.reset();
            }
            if (errMsg.empty()) errMsg = TR("wiz_bootstrap_failed");

            dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy),
                        ui::material::Error(), TR("wiz_download_failed"));
            cy += bodyFont->LegacySize + 8.0f * dp;

            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), textCol,
                        errMsg.c_str());
            cy += captionFont->LegacySize + 16.0f * dp;

            // Retry / Skip
            float btnW2 = 120.0f * dp;
            float btnH2 = 40.0f * dp;
            float totalBW = btnW2 * 2 + 12.0f * dp;
            float bx = rightX + (colW - totalBW) * 0.5f;

            ImGui::SetCursorScreenPos(ImVec2(bx, cy));
            ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(ui::material::Primary()));
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
            ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
            ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
            if (ui::material::TactileButton(TR("retry"), ImVec2(btnW2, btnH2))) {
                // Stop embedded daemon before bootstrap to avoid chain data corruption
                stopDaemonForBootstrap();
                bootstrap_ = std::make_unique<util::Bootstrap>();
                std::string dataDir = util::Platform::getDragonXDataDir();
                bootstrap_->start(dataDir);
                wizard_phase_ = WizardPhase::BootstrapInProgress;
            }
            ImGui::EndDisabled();
            ImGui::PopStyleVar();
            ImGui::PopStyleColor(3);

            ImGui::SetCursorScreenPos(ImVec2(bx + btnW2 + 12.0f * dp, cy));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
            if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(btnW2, btnH2))) {
                wizard_phase_ = WizardPhase::EncryptOffer;
            }
            ImGui::PopStyleVar();
            cy += btnH2;

        } else {
            // ---- Bootstrap offer (default view, also for non-focused) ----

            // External daemon check (async — avoids blocking UI thread).
            // On Windows isRpcPortInUse() creates a TCP socket + connect()
            // which can block for seconds when the port is not listening.
            bool externalRunning = false;
            if (isFocused) {
                static std::atomic<bool> s_extCached{false};
                static std::atomic<bool> s_checkInFlight{false};
                double& s_extLastCheck = wizardUi.external_last_check;
                double now = ImGui::GetTime();
                if (now - s_extLastCheck >= 2.0 && !s_checkInFlight.load()) {
                    s_extLastCheck = now;
                    bool embeddedRunning = isEmbeddedDaemonRunning();
                    s_checkInFlight.store(true);
                    async_tasks_.submit("wizard-external-daemon-check", [embeddedRunning](const util::AsyncTaskManager::Token& token) {
                        if (token.cancelled()) {
                            s_checkInFlight.store(false);
                            return;
                        }
                        bool inUse = daemon::EmbeddedDaemon::isRpcPortInUse();
                        if (!token.cancelled()) {
                            s_extCached.store(inUse && !embeddedRunning);
                        }
                        s_checkInFlight.store(false);
                    });
                }
                externalRunning = s_extCached.load();
            }

            if (isFocused && (externalRunning || wizard_stopping_external_)) {
                // --- External daemon warning ---
                ImU32 warnCol = ui::material::Warning();
                {
                    float iw = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_WARNING).x;
                    dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), warnCol, ICON_MD_WARNING);
                    dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx + iw + 4.0f * dp, cy), warnCol, TR("wiz_ext_daemon_running"));
                }
                cy += bodyFont->LegacySize + 4.0f * dp;
                {
                    const char* warnBody = TR("wiz_ext_daemon_warning");
                    ImVec2 ws = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, contentW, warnBody);
                    dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), textCol, warnBody, nullptr, contentW);
                    cy += ws.y + 12.0f * dp;
                }

                if (wizard_stopping_external_) {
                    const std::string ws = wizard_stop_status_.get();
                    dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol,
                                ws.c_str());
                    cy += captionFont->LegacySize + 8.0f * dp;
                } else {
                    float stopW = 150.0f * dp;
                    float skipW2 = 100.0f * dp;
                    float btnH2 = 40.0f * dp;
                    float totalBW = stopW + 12.0f * dp + skipW2;
                    float bx = rightX + (colW - totalBW) * 0.5f;

                    ImGui::SetCursorScreenPos(ImVec2(bx, cy));
                    ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(ui::material::Error()));
                    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(
                        IM_COL32(220, 60, 60, 255)));
                    ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255));
                    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
                    if (ui::material::TactileButton(TR("wiz_stop_daemon"), ImVec2(stopW, btnH2))) {
                        wizard_stopping_external_ = true;
                        wizard_stop_status_ = TR("wiz_daemon_sending_stop");
                        async_tasks_.submit("wizard-stop-external-daemon", [this](const util::AsyncTaskManager::Token& token) {
                            auto config = rpc::Connection::autoDetectConfig();
                            if (!config.rpcuser.empty() && !config.rpcpassword.empty()) {
                                auto tmp_rpc = std::make_unique<rpc::RPCClient>();
                                if (tmp_rpc->connect(config.host, config.port,
                                                     config.rpcuser, config.rpcpassword,
                                                     config.use_tls)) {
                                    sendStopCommandSafely(*tmp_rpc, "Wizard external daemon stop");
                                    tmp_rpc->disconnect();
                                }
                            }
                            wizard_stop_status_ = TR("wiz_daemon_waiting_stop");
                            for (int i = 0; i < 60 && !token.cancelled(); i++) {
                                std::this_thread::sleep_for(std::chrono::seconds(1));
                                if (!daemon::EmbeddedDaemon::isRpcPortInUse()) {
                                    wizard_stop_status_ = TR("wiz_daemon_stopped_ok");
                                    wizard_stopping_external_ = false;
                                    return;
                                }
                            }
                            if (token.cancelled()) return;
                            wizard_stop_status_ = TR("wiz_daemon_stop_failed");
                            wizard_stopping_external_ = false;
                        });
                    }
                    ImGui::PopStyleVar();
                    ImGui::PopStyleColor(3);

                    ImGui::SetCursorScreenPos(ImVec2(bx + stopW + 12.0f * dp, cy));
                    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
                    if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(skipW2, btnH2))) {
                        wizard_phase_ = WizardPhase::EncryptOffer;
                    }
                    ImGui::PopStyleVar();
                    cy += btnH2;
                }
            } else {
                // --- Normal bootstrap offer ---
                {
                    const char* bsText = TR("wiz_bootstrap_desc");
                    ImVec2 bsSize = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, contentW, bsText);
                    dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), textCol, bsText, nullptr, contentW);
                    cy += bsSize.y + 8.0f * dp;
                }

                // Trust warning
                {
                    float warnOpacity = S.drawElement("screens.first-run", "trust-warning").opacity;
                    if (warnOpacity <= 0) warnOpacity = 0.7f;
                    ImU32 warnCol = (textCol & 0x00FFFFFF) | ((ImU32)(255 * warnOpacity) << 24);
                    float iw = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_WARNING).x;
                    dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), warnCol, ICON_MD_WARNING);
                    const char* twText = TR("bootstrap_trust_warning");
                    float twWrap = contentW - iw - 4.0f * dp;
                    ImVec2 twSize = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, twWrap, twText);
                    dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iw + 4.0f * dp, cy), warnCol, twText, nullptr, twWrap);
                    cy += twSize.y + 12.0f * dp;
                }

                // Daemon status indicator (subtle, before buttons)
                {
                    bool daemonUp = isEmbeddedDaemonRunning();
                    const std::string& dStatus = getDaemonStatus();
                    ImU32 dotCol = daemonUp ? IM_COL32(76, 175, 80, 200)
                                            : IM_COL32(120, 120, 120, 160);
                    if (dStatus.find("Stopping") != std::string::npos)
                        dotCol = IM_COL32(255, 167, 38, 200);
                    float dotR = 3.5f * dp;
                    dl->AddCircleFilled(ImVec2(cx + dotR, cy + captionFont->LegacySize * 0.5f),
                                        dotR, dotCol);
                    const char* label = daemonUp ? (dStatus.find("Stopping") != std::string::npos
                                                   ? TR("bootstrap_daemon_stopping")
                                                   : TR("bootstrap_daemon_running"))
                                                : TR("bootstrap_daemon_stopped");
                    dl->AddText(captionFont, captionFont->LegacySize,
                                ImVec2(cx + dotR * 2.0f + 6.0f * dp, cy),
                                (dimCol & 0x00FFFFFF) | IM_COL32(0,0,0,140), label);
                    cy += captionFont->LegacySize + 10.0f * dp;
                }

                // Buttons (only when focused)
                if (isFocused) {
                    float dlBtnW = 150.0f * dp;
                    float mirrorW = 150.0f * dp;
                    float skipW2 = 80.0f * dp;
                    float btnH2 = 40.0f * dp;
                    float totalBW = dlBtnW + 8.0f * dp + mirrorW + 8.0f * dp + skipW2;
                    float bx = rightX + (colW - totalBW) * 0.5f;

                    // --- Download button (main / Cloudflare) ---
                    ImGui::SetCursorScreenPos(ImVec2(bx, cy));
                    ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(ui::material::Primary()));
                    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
                    ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
                    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
                    ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
                    if (ui::material::TactileButton(TR("download"), ImVec2(dlBtnW, btnH2))) {
                        // Stop embedded daemon before bootstrap to avoid chain data corruption
                        stopDaemonForBootstrap();
                        bootstrap_ = std::make_unique<util::Bootstrap>();
                        std::string dataDir = util::Platform::getDragonXDataDir();
                        bootstrap_->start(dataDir);
                        wizard_phase_ = WizardPhase::BootstrapInProgress;
                    }
                    ImGui::EndDisabled();
                    ImGui::PopStyleVar();
                    ImGui::PopStyleColor(3);

                    // --- Mirror Download button ---
                    ImGui::SetCursorScreenPos(ImVec2(bx + dlBtnW + 8.0f * dp, cy));
                    ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(ui::material::Surface()));
                    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
                    ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnSurface()));
                    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
                    ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
                    if (ui::material::TactileButton(TR("bootstrap_mirror"), ImVec2(mirrorW, btnH2))) {
                        stopDaemonForBootstrap();
                        bootstrap_ = std::make_unique<util::Bootstrap>();
                        std::string dataDir = util::Platform::getDragonXDataDir();
                        std::string mirrorUrl = std::string(util::Bootstrap::kMirrorUrl) + "/" + util::Bootstrap::kZipName;
                        bootstrap_->start(dataDir, mirrorUrl);
                        wizard_phase_ = WizardPhase::BootstrapInProgress;
                    }
                    ImGui::EndDisabled();
                    if (ImGui::IsItemHovered()) {
                        ui::material::Tooltip(TR("bootstrap_mirror_tooltip"));
                    }
                    ImGui::PopStyleVar();
                    ImGui::PopStyleColor(3);

                    // --- Skip button ---
                    ImGui::SetCursorScreenPos(ImVec2(bx + dlBtnW + 8.0f * dp + mirrorW + 8.0f * dp, cy));
                    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
                    if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(skipW2, btnH2))) {
                        wizard_phase_ = WizardPhase::EncryptOffer;
                    }
                    ImGui::PopStyleVar();
                    cy += btnH2;
                }
            }
        }

        cy += cardPad;
        // Lock card height to the tallest content ever seen (but not when collapsed)
        float& card1MaxH = wizardUi.card1_max_h;
        if (isCollapsed) {
            card1Bot = card1Top + (cy - card1Top);
        } else {
            card1MaxH = std::max(card1MaxH, cy - card1Top);
            card1Bot = card1Top + card1MaxH;
        }

        finalizeCard(rightX, colW, card1Top, card1Bot, state);
    }


    // ======================= CARD 2: Encrypt + PIN =======================
    float card2Top = card1Bot + gap;
    float card2Bot;
    {
        int state = cardState(2);
        bool isFocused = (state == 1);
        float cx = rightX + cardPad;
        float cy = card2Top + cardPad;
        float contentW = colW - 2 * cardPad;

        // Pre-start daemon when encrypt card becomes focused so it's ready
        // by the time the user finishes typing their passphrase
        if (isFocused) {
            bool& wiz_daemon_prestarted = wizardUi.daemon_prestarted;
            if (!wiz_daemon_prestarted) {
                wiz_daemon_prestarted = true;
                if (!state_.connected && isUsingEmbeddedDaemon() && !isEmbeddedDaemonRunning()) {
                    startEmbeddedDaemon();
                }
                if (!state_.connected && !connection_in_progress_) {
                    tryConnect();
                }
            }
        }

        // Step indicator
        {
            float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
            dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, TR("wiz_step3"));
            cy += captionFont->LegacySize + 4.0f * dp;
        }

        // Title (changes for PinSetup sub-state)
        {
            const char* t = (isFocused && wizard_phase_ == WizardPhase::PinSetup)
                ? TR("wiz_pin_title") : TR("wiz_encryption");
            dl->AddText(titleFont, titleFont->LegacySize, ImVec2(cx, cy), textCol, t);
            cy += titleFont->LegacySize + 6.0f * dp;
        }

        // Separator
        dl->AddLine(ImVec2(cx, cy), ImVec2(cx + contentW, cy),
                    (textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp);
        cy += 10.0f * dp;

        // --- Content varies by sub-state ---
        if (isFocused && state_.isEncrypted()) {
            // ---- Wallet already encrypted ----
            {
                ImU32 okCol = ui::material::Secondary();
                float iw = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_VERIFIED_USER).x;
                dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), okCol, ICON_MD_VERIFIED_USER);
                dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx + iw + 6.0f * dp, cy), okCol, TR("wiz_already_encrypted"));
                cy += bodyFont->LegacySize + 12.0f * dp;
            }
            {
                const char* desc = TR("wiz_already_encrypted_desc");
                ImVec2 ds = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, contentW, desc);
                dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), textCol, desc, nullptr, contentW);
                cy += ds.y + 20.0f * dp;
            }

            // Continue button — skip to Done
            float btnW2 = 140.0f * dp;
            float btnH2 = 40.0f * dp;
            float bx = rightX + (colW - btnW2) * 0.5f;
            ImGui::SetCursorScreenPos(ImVec2(bx, cy));
            ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(ui::material::Primary()));
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
            ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
            if (ui::material::TactileButton(TR("wiz_continue"), ImVec2(btnW2, btnH2))) {
                wizard_phase_ = WizardPhase::Done;
                settings_->setWizardCompleted(true);
                settings_->save();
            }
            ImGui::PopStyleVar();
            ImGui::PopStyleColor(3);
            cy += btnH2;

        } else if (isFocused) {
            // ---- Encryption offer + optional PIN (combined) ----
            {
                const char* encDesc = TR("wiz_encrypt_desc");
                ImVec2 edSize = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, contentW, encDesc);
                dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), textCol, encDesc, nullptr, contentW);
                cy += edSize.y + 6.0f * dp;
            }
            {
                ImU32 warnCol2 = ui::material::Warning();
                float iw = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_WARNING).x;
                dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), warnCol2, ICON_MD_WARNING);
                const char* warnLoss = TR("wiz_encrypt_warning");
                float wlWrap = contentW - iw - 4.0f * dp;
                ImVec2 wlSize = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, wlWrap, warnLoss);
                dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx + iw + 4.0f * dp, cy), warnCol2, warnLoss, nullptr, wlWrap);
                cy += wlSize.y + 8.0f * dp;
            }

            // Passphrase input
            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, TR("wiz_passphrase"));
            cy += captionFont->LegacySize + 4.0f * dp;

            ImGui::SetCursorScreenPos(ImVec2(cx, cy));
            ImGui::PushItemWidth(contentW);
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * dp);
            ImGui::InputText("##wiz_pass", encrypt_pass_buf_, sizeof(encrypt_pass_buf_),
                             ImGuiInputTextFlags_Password);
            ImGui::PopStyleVar();
            ImGui::PopItemWidth();
            cy += 36.0f * dp + 6.0f * dp;

            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, TR("wiz_confirm"));
            cy += captionFont->LegacySize + 4.0f * dp;

            ImGui::SetCursorScreenPos(ImVec2(cx, cy));
            ImGui::PushItemWidth(contentW);
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * dp);
            ImGui::InputText("##wiz_confirm", encrypt_confirm_buf_, sizeof(encrypt_confirm_buf_),
                             ImGuiInputTextFlags_Password);
            ImGui::PopStyleVar();
            ImGui::PopItemWidth();
            cy += 36.0f * dp + 6.0f * dp;

            // Strength meter
            {
                size_t len = strlen(encrypt_pass_buf_);
                const char* strengthLabel = TR("wiz_strength_weak");
                ImU32 strengthCol = ui::material::Error();
                float strengthPct = 0.25f;

                if (len >= 16) {
                    strengthLabel = TR("wiz_strength_strong"); strengthCol = ui::material::Secondary(); strengthPct = 1.0f;
                } else if (len >= 12) {
                    strengthLabel = TR("wiz_strength_good"); strengthCol = ui::material::Secondary(); strengthPct = 0.75f;
                } else if (len >= 8) {
                    strengthLabel = TR("wiz_strength_fair"); strengthCol = ui::material::Warning(); strengthPct = 0.5f;
                }

                float sBarH = 4.0f * dp, sBarR = 2.0f * dp;
                dl->AddRectFilled(ImVec2(cx, cy), ImVec2(cx + contentW, cy + sBarH),
                                  IM_COL32(255,255,255,30), sBarR);
                if (len > 0) {
                    dl->AddRectFilled(ImVec2(cx, cy), ImVec2(cx + contentW * strengthPct, cy + sBarH),
                                      strengthCol, sBarR);
                }
                cy += sBarH + 4.0f * dp;

                char slabel[64];
                snprintf(slabel, sizeof(slabel), TR("wiz_strength"), strengthLabel);
                dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, slabel);
                cy += captionFont->LegacySize + 10.0f * dp;
            }

            // Feedback on why Encrypt is disabled
            {
                size_t pLen = strlen(encrypt_pass_buf_);
                if (pLen > 0 && pLen < 8) {
                    char fb[80];
                    snprintf(fb, sizeof(fb), TR("wiz_pass_too_short"), pLen);
                    dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
                                ui::material::Error(), fb);
                    cy += captionFont->LegacySize + 6.0f * dp;
                } else if (pLen >= 8 && strlen(encrypt_confirm_buf_) > 0 &&
                           strcmp(encrypt_pass_buf_, encrypt_confirm_buf_) != 0) {
                    dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
                                ui::material::Error(), TR("wiz_pass_mismatch"));
                    cy += captionFont->LegacySize + 6.0f * dp;
                }
            }

            // ---- Optional PIN section ----
            cy += 4.0f * dp;
            dl->AddLine(ImVec2(cx, cy), ImVec2(cx + contentW, cy),
                        (textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp);
            cy += 8.0f * dp;

            {
                const char* pinTitle = TR("wiz_pin_optional");
                dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), textCol, pinTitle);
                cy += captionFont->LegacySize + 4.0f * dp;
            }

            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, TR("wiz_pin_label"));
            cy += captionFont->LegacySize + 4.0f * dp;

            ImGui::SetCursorScreenPos(ImVec2(cx, cy));
            ImGui::PushItemWidth(contentW);
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * dp);
            ImGui::InputText("##wiz_pin", wizard_pin_buf_, sizeof(wizard_pin_buf_),
                             ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
            ImGui::PopStyleVar();
            ImGui::PopItemWidth();
            cy += 36.0f * dp + 6.0f * dp;

            dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, TR("wiz_pin_confirm"));
            cy += captionFont->LegacySize + 4.0f * dp;

            ImGui::SetCursorScreenPos(ImVec2(cx, cy));
            ImGui::PushItemWidth(contentW);
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * dp);
            ImGui::InputText("##wiz_pin_confirm", wizard_pin_confirm_buf_, sizeof(wizard_pin_confirm_buf_),
                             ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
            ImGui::PopStyleVar();
            ImGui::PopItemWidth();
            cy += 36.0f * dp + 6.0f * dp;

            // PIN validation feedback
            {
                std::string pinStr(wizard_pin_buf_);
                if (!pinStr.empty() && !util::SecureVault::isValidPin(pinStr)) {
                    dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
                                ui::material::Error(), TR("wiz_pin_invalid"));
                    cy += captionFont->LegacySize + 6.0f * dp;
                } else if (!pinStr.empty() && strlen(wizard_pin_confirm_buf_) > 0 &&
                           pinStr != std::string(wizard_pin_confirm_buf_)) {
                    dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
                                ui::material::Error(), TR("wiz_pin_mismatch"));
                    cy += captionFont->LegacySize + 6.0f * dp;
                }
            }

            // Status
            if (!encrypt_status_.empty()) {
                dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
                            ui::material::Warning(), encrypt_status_.c_str());
                cy += captionFont->LegacySize + 6.0f * dp;
            }

            // Warn + block if the passphrase has leading/trailing whitespace. Silently trimming it would
            // change the passphrase the user believes they set and lock them out on the next unlock.
            bool passEdgeSpace = false;
            if (size_t pl = strlen(encrypt_pass_buf_)) {
                char a = encrypt_pass_buf_[0], b = encrypt_pass_buf_[pl - 1];
                passEdgeSpace = (a == ' ' || a == '\t' || b == ' ' || b == '\t');
            }
            if (passEdgeSpace) {
                dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
                            ui::material::Error(), TR("wiz_pass_spaces"));
                cy += captionFont->LegacySize + 6.0f * dp;
            }

            // Buttons
            {
                bool passValid = strlen(encrypt_pass_buf_) >= 8 &&
                                 strcmp(encrypt_pass_buf_, encrypt_confirm_buf_) == 0 &&
                                 !passEdgeSpace;
                // PIN is optional: if entered, must be valid + confirmed
                std::string pinStr(wizard_pin_buf_);
                bool pinEntered = !pinStr.empty();
                bool pinOk = !pinEntered ||
                             (util::SecureVault::isValidPin(pinStr) &&
                              pinStr == std::string(wizard_pin_confirm_buf_));
                bool canEncrypt = passValid && pinOk && !encrypt_in_progress_;

                float encBtnW = 180.0f * dp;
                float skipW2 = 80.0f * dp;
                float btnH2 = 40.0f * dp;
                float totalBW = encBtnW + 12.0f * dp + skipW2;
                float bx = rightX + (colW - totalBW) * 0.5f;

                ImGui::SetCursorScreenPos(ImVec2(bx, cy));
                ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(
                    canEncrypt ? ui::material::Primary() : IM_COL32(128,128,128,128)));
                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
                ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
                ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
                ImGui::BeginDisabled(!canEncrypt);
                if (ui::material::TactileButton(TR("wiz_encrypt_continue"), ImVec2(encBtnW, btnH2))) {
                    // Save passphrase + optional PIN for background processing
                    wallet_security_.beginDeferredEncryption(
                        std::string(encrypt_pass_buf_),
                        (pinEntered && pinOk) ? pinStr : std::string());
                    // Persist that encryption was requested (never the passphrase) so a quit/crash or
                    // failed daemon connect before it applies isn't silent — reconciled on the next
                    // connect in refreshWalletEncryptionState (W2-2). Saved with the wizard state below.
                    settings_->setEncryptionPending(true);

                    // Clear sensitive buffers
                    memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
                    memset(encrypt_confirm_buf_, 0, sizeof(encrypt_confirm_buf_));
                    memset(wizard_pin_buf_, 0, sizeof(wizard_pin_buf_));
                    memset(wizard_pin_confirm_buf_, 0, sizeof(wizard_pin_confirm_buf_));

                    // Start daemon + finish wizard immediately
                    if (!isEmbeddedDaemonRunning() && isUsingEmbeddedDaemon()) {
                        if (!startEmbeddedDaemon()) {
                            ui::Notifications::instance().warning(
                                TR("wizard_daemon_start_failed"));
                        }
                    }
                    tryConnect();
                    wizard_phase_ = WizardPhase::Done;
                    settings_->setWizardCompleted(true);
                    settings_->save();
                    ui::Notifications::instance().info(TR("wiz_encrypt_bg"));
                }
                ImGui::EndDisabled();
                ImGui::PopStyleVar();
                ImGui::PopStyleColor(3);

                ImGui::SetCursorScreenPos(ImVec2(bx + encBtnW + 12.0f * dp, cy));
                ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
                if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(skipW2, btnH2))) {
                    static bool s_skipEncConfirm = false;
                    if (!s_skipEncConfirm) {
                        // Skipping stores private keys UNENCRYPTED — require a confirming second click.
                        s_skipEncConfirm = true;
                        encrypt_status_ = TR("wiz_skip_confirm");
                    } else {
                        s_skipEncConfirm = false;
                        // Skipping leaves the wallet UNENCRYPTED — wipe the passphrase/PIN the user may have
                        // typed so it doesn't linger in these process-lifetime buffers (only the Encrypt
                        // path cleared them before). (L-07)
                        memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
                        memset(encrypt_confirm_buf_, 0, sizeof(encrypt_confirm_buf_));
                        memset(wizard_pin_buf_, 0, sizeof(wizard_pin_buf_));
                        memset(wizard_pin_confirm_buf_, 0, sizeof(wizard_pin_confirm_buf_));
                        wizard_phase_ = WizardPhase::Done;
                        settings_->setWizardCompleted(true);
                        settings_->save();
                        if (!isEmbeddedDaemonRunning() && isUsingEmbeddedDaemon()) {
                            if (!startEmbeddedDaemon()) {
                                ui::Notifications::instance().warning(
                                    TR("wizard_daemon_start_failed"));
                            }
                        }
                        tryConnect();
                    }
                }
                ImGui::PopStyleVar();
                cy += btnH2;
            }
        } else {
            // ---- Not focused: show static description ----
            const char* encDesc = TR("wiz_encrypt_desc");
            ImVec2 edSize = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, contentW, encDesc);
            dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), dimCol, encDesc, nullptr, contentW);
            cy += edSize.y + 6.0f * dp;
        }

        cy += cardPad;
        {
            card2Bot = card2Top + (cy - card2Top);
            // Stretch card 2 so its bottom sits flush with card 0 (left column)
            if (card0Bot > card2Bot)
                card2Bot = card0Bot;
        }

        finalizeCard(rightX, colW, card2Top, card2Bot, state);
    }

    // --- Deferred Card 0 finalization: match right column total height ---
    // Only for the focused/upcoming Appearance card; a completed one was already finalized
    // above as a compact pill and must not be re-stretched.
    if (cardState(0) != 2) {
        float rightColBot = card2Bot;
        if (rightColBot > card0Bot) card0Bot = rightColBot;
        finalizeCard(leftX, colW, card0Top, card0Bot, cardState(0));
    }

    // Merge channels: backgrounds → content → overlays
    dl->ChannelsMerge();

    // Measure this frame's content height (feeds next frame's scroll clamp) and, when it overflows the
    // window, draw a slim scroll indicator so the off-screen content is discoverable.
    {
        float contentBottom = std::max(card0Bot, std::max(card1Bot, card2Bot));
        // Subtract vCenter back out: everything below the header was shifted down by it, so the raw
        // span includes it. We want s_wizContentH to be the true (un-centered) content height, or the
        // vertical-centering above would feed on itself and oscillate frame-to-frame.
        s_wizContentH = (contentBottom - winPos.y + scrollY - vCenter) + 24.0f * dp;
        if (wizMaxScroll > 0.0f && s_wizContentH > 0.0f) {
            float trackH = winSize.y - 8.0f * dp;
            float thumbH = std::min(trackH, std::max(32.0f * dp, trackH * (winSize.y / s_wizContentH)));
            float thumbY = winPos.y + 4.0f * dp + (trackH - thumbH) * (scrollY / wizMaxScroll);
            float barX   = winPos.x + winSize.x - 6.0f * dp;
            dl->AddRectFilled(ImVec2(barX, thumbY), ImVec2(barX + 3.0f * dp, thumbY + thumbH),
                              ui::material::WithAlpha(ui::material::OnSurface(), 55), 1.5f * dp);
        }
    }

    ImGui::End();
}

} // namespace dragonx
