// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#include "settings_page.h"
#include "../../app.h"
#include "../../config/version.h"
#include "../../config/settings.h"
#include "../../wallet/lite_wallet_lifecycle_ui_adapter.h"
#include "../../wallet/lite_wallet_server_selection_adapter.h"
#include "../../wallet/lite_wallet_controller.h"
#include <sodium.h>
#include "../../util/logger.h"
#include "../windows/balance_tab.h"
#include "../windows/console_tab.h"
#include "../../util/i18n.h"
#include "../../util/platform.h"
#include "../../resources/embedded_resources.h"
#include <ctime>
#include "../../rpc/rpc_client.h"
#include "../../rpc/connection.h"
#include "../../rpc/rpc_worker.h"
#include "../theme.h"
#include "../layout.h"
#include "../schema/ui_schema.h"
#include "../schema/skin_manager.h"
#include "../notifications.h"
#include "../effects/imgui_acrylic.h"
#include "../effects/theme_effects.h"
#include "../effects/low_spec.h"
#include "../effects/scroll_fade_shader.h"
#include "../material/draw_helpers.h"
#include "../material/type.h"
#include "../material/colors.h"
#include "../windows/validate_address_dialog.h"
#include "../windows/shield_dialog.h"
#include "../windows/request_payment_dialog.h"
#include "../windows/block_info_dialog.h"
#include "../windows/export_all_keys_dialog.h"
#include "../windows/export_transactions_dialog.h"
#include "../windows/bootstrap_download_dialog.h"
#include "../windows/daemon_download_dialog.h"
#include "../windows/wallets_dialog.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include <nlohmann/json.hpp>
#include <vector>
#include <set>
#include <filesystem>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstring>

namespace dragonx {
namespace ui {

using namespace material;

// Top of the Acrylic slider's range (the blur-radius multiplier at 100%). The panels frost at full
// alpha now, so this only controls how BLURRY the max is — kept gentle (the old max of 4.0 was way
// too strong). The slider still reads 0–100%; this is just what 100% maps to.
static constexpr float kAcrylicMaxBlur = 1.25f;

// Helper: build "TranslatedLabel##id" for ImGui widgets that use label as ID
static std::string TrId(const char* tr_key, const char* id) {
    std::string s = TR(tr_key);
    s += "##";
    s += id;
    return s;
}

// ============================================================================
// Settings state loaded from config::Settings on first render
// ============================================================================
struct LowSpecSnapshot {
    bool valid = false;
    bool acrylic_enabled;
    float blur_amount;
    float ui_opacity;
    float window_opacity;
    bool theme_effects_enabled;
    bool scanline_enabled;
};

struct SettingsPageState {
    bool initialized = false;
    int language_index = 0;
    bool save_ztxs = true;
    bool allow_custom_fees = false;
    bool auto_shield = false;
    bool fetch_prices = true;
    bool use_tor = false;
    char rpc_host[128] = DRAGONX_DEFAULT_RPC_HOST;
    char rpc_port[16] = DRAGONX_DEFAULT_RPC_PORT;
    char rpc_user[64] = "";
    char rpc_password[64] = "";
    bool rpc_plaintext_remote = false;
    char tx_explorer[256] = "https://explorer.dragonx.is/tx/";
    char addr_explorer[256] = "https://explorer.dragonx.is/address/";
    bool acrylic_enabled = true;
    float blur_amount = 1.5f;
    float noise_opacity = 0.5f;
    float ui_opacity = 1.0f;
    float window_opacity = 1.0f;
    std::string balance_layout = "classic";
    bool scanline_enabled = true;
    bool theme_effects_enabled = true;
    bool gradient_background = false;
    bool low_spec_mode = false;
    bool reduce_motion = false;
    float font_scale = 1.0f;
    LowSpecSnapshot low_spec_snapshot;
    bool keep_daemon_running = false;
    bool stop_external_daemon = false;
    bool lite_lifecycle_expanded = false;
    int lite_lifecycle_operation = 0;
    char lite_wallet_path[256] = "";
    char lite_lifecycle_passphrase[128] = "";
    char lite_restore_seed[512] = "";
    int lite_restore_birthday = 0;
    int lite_restore_account = 0;
    bool lite_restore_overwrite = false;
    std::string lite_lifecycle_status;
    std::string lite_lifecycle_summary;
    // True while an async create/open/restore is in flight (Settings drives the controller's
    // async lifecycle path so a flaky server never freezes the UI; the result is polled each frame).
    bool lite_lifecycle_pending = false;
    // Backup & keys (only populated for an open lite wallet). lite_export_secret holds the
    // revealed seed/private-keys backup and is SECRET: securely wiped on hide / new export /
    // import. lite_import_key is the import input buffer (wiped right after submission).
    std::string lite_export_secret;
    std::string lite_export_label;
    bool lite_export_is_seed = false;        // revealed secret is a seed (show birthday + save-to-file)
    unsigned long long lite_export_birthday = 0;  // seed birthday (shown with the seed)
    char lite_import_key[512] = "";
    std::string lite_backup_status;
    // Encryption passphrase inputs (SECRET: zeroed right after each action). lite_enc_pass is
    // reused for Encrypt (unencrypted wallet) and Unlock (locked wallet); lite_dec_pass for Decrypt.
    char lite_enc_pass[128] = "";
    char lite_dec_pass[128] = "";
    std::string lite_encryption_status;
    bool mine_when_idle = false;
    int mine_idle_delay = 120;
    bool idle_thread_scaling = false;
    int idle_threads_active = 0;
    int idle_threads_idle = 0;
    bool verbose_logging = false;
    std::set<std::string> debug_categories;
    bool debug_cats_dirty = false;
    bool debug_expanded = false;
    // Debug-options gate: a confirmation + warning (and, when a PIN/passphrase is set, re-auth) is
    // required before the debug dropdown reveals its options — once per session (passing it once
    // unlocks the dropdown until the app restarts).
    bool debug_gate_open = false;
    bool debug_gate_passed = false;
    bool debug_gate_verifying = false;
    char debug_gate_buf[128] = {0};
    std::string debug_gate_err;
    float debug_gate_err_timer = 0.0f;
    bool effects_expanded = false;
    bool tools_expanded = false;
    bool rpc_expanded = false;             // Node & Security: reveal the RPC connection fields
    bool confirm_clear_ztx = false;
    bool confirm_delete_blockchain = false;
    bool confirm_rescan = false;
    // Rescan dialog: probe the node's available block range so a bootstrapped/pruned node gets a
    // runtime rescan from a snapshot-available height instead of the (failing) -rescan-from-genesis.
    bool rescan_height_detecting = false;
    bool rescan_height_detected = false;
    bool rescan_full_history = true;     // genesis present → traditional -rescan restart
    int  rescan_start_height = 0;        // editable pre-fill for the runtime rescan
    bool confirm_repair_wallet = false;
    bool confirm_reinstall_daemon = false;
    // Cached daemon-binary status for the "daemon binary" panel (loaded once / on Refresh,
    // since reading the installed binary to scan its version is a one-off disk read).
    bool daemon_info_loaded = false;
    dragonx::resources::DaemonBinaryInfo installed_daemon;
    dragonx::resources::BundledDaemonInfo bundled_daemon;
    bool confirm_restart_daemon = false;
    bool confirm_lite_redownload = false;
    effects::ScrollFadeShader fade_shader;
};

static SettingsPageState s_settingsState;

// Low-spec mode enter: snapshot the current effects prefs, then force the
// low-cost overrides. When applyEffects is true, also push the overrides to
// the live effects subsystems (the runtime-sync path passes false since it
// only mirrors an already-applied hotkey toggle into the checkbox state).
static void enterLowSpec(bool applyEffects) {
    s_settingsState.low_spec_snapshot.valid = true;
    s_settingsState.low_spec_snapshot.acrylic_enabled = s_settingsState.acrylic_enabled;
    s_settingsState.low_spec_snapshot.blur_amount = s_settingsState.blur_amount;
    s_settingsState.low_spec_snapshot.ui_opacity = s_settingsState.ui_opacity;
    s_settingsState.low_spec_snapshot.window_opacity = s_settingsState.window_opacity;
    s_settingsState.low_spec_snapshot.theme_effects_enabled = s_settingsState.theme_effects_enabled;
    s_settingsState.low_spec_snapshot.scanline_enabled = s_settingsState.scanline_enabled;
    s_settingsState.acrylic_enabled = false;
    s_settingsState.blur_amount = 0.0f;
    s_settingsState.ui_opacity = 1.0f;
    s_settingsState.window_opacity = 1.0f;
    s_settingsState.theme_effects_enabled = false;
    s_settingsState.scanline_enabled = false;
    if (applyEffects) {
        effects::ImGuiAcrylic::ApplyBlurAmount(0.0f);
        effects::ImGuiAcrylic::SetUIOpacity(1.0f);
        effects::ThemeEffects::instance().setEnabled(false);
        ConsoleTab::s_scanline_enabled = false;
    }
}

// Low-spec mode exit: restore the previously snapshotted effects prefs and
// clear the snapshot. When applyEffects is true, also push them to the live
// effects subsystems. Callers guard this on low_spec_snapshot.valid.
static void exitLowSpec(bool applyEffects) {
    s_settingsState.blur_amount = s_settingsState.low_spec_snapshot.blur_amount;
    s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
    s_settingsState.ui_opacity = s_settingsState.low_spec_snapshot.ui_opacity;
    s_settingsState.window_opacity = s_settingsState.low_spec_snapshot.window_opacity;
    s_settingsState.theme_effects_enabled = s_settingsState.low_spec_snapshot.theme_effects_enabled;
    s_settingsState.scanline_enabled = s_settingsState.low_spec_snapshot.scanline_enabled;
    if (applyEffects) {
        effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount);
        effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity);
        effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled);
        ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled;
    }
    s_settingsState.low_spec_snapshot.valid = false;
}

// Count whitespace-separated words in a (seed) buffer — used to validate/guide restore input.
static int liteSeedWordCount(const char* s) {
    int words = 0;
    bool inWord = false;
    for (; s && *s; ++s) {
        const bool space = std::isspace(static_cast<unsigned char>(*s)) != 0;
        if (space) inWord = false;
        else if (!inWord) { inWord = true; ++words; }
    }
    return words;
}

static wallet::LiteWalletLifecycleOperation liteLifecycleOperationFromPageState() {
    switch (s_settingsState.lite_lifecycle_operation) {
        case 1: return wallet::LiteWalletLifecycleOperation::OpenExisting;
        case 2: return wallet::LiteWalletLifecycleOperation::RestoreFromSeed;
        default: return wallet::LiteWalletLifecycleOperation::CreateNew;
    }
}


static void evaluateLiteLifecycleRequestFromPageState(App* app) {
    if (!app || !app->settings()) return;

    wallet::LiteWalletLifecycleUiExecutionInput input;
    input.capabilities = app->walletCapabilities();
    input.settingsLoaded = true;
    input.requirePersistedServerSelectionIntent = true;
    input.ui.selectedServerDisplayReady = true;
    input.ui.lifecycleUiOwnerReady = true;
    input.ui.operationConfirmed = true;
    input.ui.privateDataRedactionReady = true;
    input.ui.syncPlannerFeedReady = true;
    input.request.requestProvided = true;
    input.request.operation = liteLifecycleOperationFromPageState();

    switch (input.request.operation) {
        case wallet::LiteWalletLifecycleOperation::CreateNew:
            input.request.createRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
            break;
        case wallet::LiteWalletLifecycleOperation::OpenExisting:
            input.request.openRequest.walletPath = s_settingsState.lite_wallet_path;
            input.request.openRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
            break;
        case wallet::LiteWalletLifecycleOperation::RestoreFromSeed:
            input.request.restoreRequest.walletPath = s_settingsState.lite_wallet_path;
            input.request.restoreRequest.seedPhrase = s_settingsState.lite_restore_seed;
            input.request.restoreRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
            input.request.restoreRequest.birthday = static_cast<unsigned long long>(std::max(0, s_settingsState.lite_restore_birthday));
            input.request.restoreRequest.account = static_cast<unsigned long long>(std::max(0, s_settingsState.lite_restore_account));
            input.request.restoreRequest.overwrite = s_settingsState.lite_restore_overwrite;
            break;
    }

    // Wipe ALL secret material when leaving this function, on every path (real execution,
    // validation-only fallback, or early return): the UI char buffers AND the std::string
    // copies inside `input.request.*Request`. The controller wipes its own by-value request
    // copy, but these page-local copies are separate; leaving them would defeat the wipe.
    struct LiteSecretScrubber {
        wallet::LiteWalletLifecycleUiExecutionInput& in;
        ~LiteSecretScrubber() {
            sodium_memzero(s_settingsState.lite_lifecycle_passphrase,
                           sizeof(s_settingsState.lite_lifecycle_passphrase));
            sodium_memzero(s_settingsState.lite_restore_seed,
                           sizeof(s_settingsState.lite_restore_seed));
            wallet::secureWipeLiteSecret(in.request.createRequest.passphrase);
            wallet::secureWipeLiteSecret(in.request.openRequest.passphrase);
            wallet::secureWipeLiteSecret(in.request.restoreRequest.seedPhrase);
            wallet::secureWipeLiteSecret(in.request.restoreRequest.passphrase);
        }
    } liteSecretScrubber{input};

    // Open/Restore both target a wallet path; reject an empty/whitespace-only one early so we
    // never dispatch an unusable request (the scrubber above still wipes secrets on this return).
    if (input.request.operation == wallet::LiteWalletLifecycleOperation::OpenExisting ||
        input.request.operation == wallet::LiteWalletLifecycleOperation::RestoreFromSeed) {
        std::string trimmedPath(s_settingsState.lite_wallet_path);
        const auto first = trimmedPath.find_first_not_of(" \t\r\n");
        if (first == std::string::npos) {
            s_settingsState.lite_lifecycle_status = "Enter a wallet path";
            s_settingsState.lite_lifecycle_summary.clear();
            return;
        }
    }

    // Restore needs a complete 24-word seed; reject early (the scrubber above still wipes the
    // entered secret on this return path).
    if (input.request.operation == wallet::LiteWalletLifecycleOperation::RestoreFromSeed) {
        const int words = liteSeedWordCount(s_settingsState.lite_restore_seed);
        if (words != 24) {
            s_settingsState.lite_lifecycle_status =
                "Enter all 24 seed words to restore (got " + std::to_string(words) + ")";
            s_settingsState.lite_lifecycle_summary.clear();
            return;
        }
    }

    // When a linked lite backend is present, execute the operation for real through the App-owned
    // controller — ASYNCHRONOUSLY, with server failover, so an unreachable/flaky server never
    // freezes the UI. The request (with its secrets) is moved into the controller; the page polls
    // the outcome each frame in renderLiteLifecyclePending() below. (Secret wiping of the page char
    // buffers + the moved-from request copies is handled by liteSecretScrubber at function exit.)
    if (auto* lite = app->liteWallet()) {
        bool started = false;
        switch (input.request.operation) {
            case wallet::LiteWalletLifecycleOperation::CreateNew:
                started = lite->beginCreateWalletAsync(std::move(input.request.createRequest));
                break;
            case wallet::LiteWalletLifecycleOperation::OpenExisting:
                started = lite->beginOpenWalletAsync(std::move(input.request.openRequest));
                break;
            case wallet::LiteWalletLifecycleOperation::RestoreFromSeed:
                started = lite->beginRestoreWalletAsync(std::move(input.request.restoreRequest));
                break;
        }
        if (started) {
            s_settingsState.lite_lifecycle_pending = true;
            s_settingsState.lite_lifecycle_status = TR("lite_working");
            s_settingsState.lite_lifecycle_summary.clear();
        } else {
            // Rejected before any thread launched (wallet already open, an attempt in flight, or no
            // usable server). The controller's status carries the reason.
            s_settingsState.lite_lifecycle_status = lite->status().message.empty()
                ? "Could not start the operation"
                : lite->status().message;
            Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
        }
        return;
    }

    // No linked lite backend to execute against (full-node build, or the lite backend
    // failed to load / rollout-disabled). The live path above returns when a backend is
    // present, so reaching here means there is nothing to run.
    s_settingsState.lite_lifecycle_summary.clear();
    s_settingsState.lite_lifecycle_status = "Lite wallet backend unavailable";
    Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
}

// (APPEARANCE card now uses ChannelsSplit like all other cards)

static void loadSettingsPageState(config::Settings* settings) {
    if (!settings) return;

    s_settingsState.save_ztxs = settings->getSaveZtxs();
    s_settingsState.allow_custom_fees = settings->getAllowCustomFees();
    s_settingsState.auto_shield = settings->getAutoShield();
    s_settingsState.fetch_prices = settings->getFetchPrices();
    s_settingsState.use_tor = settings->getUseTor();

    strncpy(s_settingsState.tx_explorer, settings->getTxExplorerUrl().c_str(), sizeof(s_settingsState.tx_explorer) - 1);
    strncpy(s_settingsState.addr_explorer, settings->getAddressExplorerUrl().c_str(), sizeof(s_settingsState.addr_explorer) - 1);

    auto& i18n = util::I18n::instance();
    const auto& languages = i18n.getAvailableLanguages();
    std::string current_lang = settings->getLanguage();
    if (current_lang.empty()) current_lang = "en";

    s_settingsState.language_index = 0;
    int idx = 0;
    for (const auto& lang : languages) {
        if (lang.first == current_lang) {
            s_settingsState.language_index = idx;
            break;
        }
        idx++;
    }

    // Load blur amount directly from saved multiplier, clamped to the (now gentler) slider max so a
    // value saved under the old 0–4 range maps into 0–100% instead of pinning far past the top.
    s_settingsState.blur_amount = std::min(settings->getBlurMultiplier(), kAcrylicMaxBlur);
    s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
    s_settingsState.ui_opacity = settings->getUIOpacity();
    s_settingsState.window_opacity = settings->getWindowOpacity();
    s_settingsState.noise_opacity = settings->getNoiseOpacity();

    s_settingsState.gradient_background = settings->getGradientBackground();

    s_settingsState.balance_layout = settings->getBalanceLayout();
    s_settingsState.scanline_enabled = settings->getScanlineEnabled();
    ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled;
    s_settingsState.theme_effects_enabled = settings->getThemeEffectsEnabled();
    s_settingsState.low_spec_mode = settings->getLowSpecMode();
    effects::setLowSpecMode(s_settingsState.low_spec_mode);
    s_settingsState.reduce_motion = settings->getReduceMotion();
    s_settingsState.font_scale = settings->getFontScale();
    Layout::setUserFontScale(s_settingsState.font_scale);  // sync with Layout on load
    s_settingsState.keep_daemon_running = settings->getKeepDaemonRunning();
    s_settingsState.stop_external_daemon = settings->getStopExternalDaemon();
    // Lite-server selection is managed entirely by the Network tab (not the Settings page).
    s_settingsState.mine_when_idle = settings->getMineWhenIdle();
    s_settingsState.mine_idle_delay = settings->getMineIdleDelay();
    s_settingsState.idle_thread_scaling = settings->getIdleThreadScaling();
    s_settingsState.idle_threads_active = settings->getIdleThreadsActive();
    s_settingsState.idle_threads_idle   = settings->getIdleThreadsIdle();
    s_settingsState.verbose_logging = settings->getVerboseLogging();
    s_settingsState.debug_categories = settings->getDebugCategories();
    s_settingsState.debug_cats_dirty = false;
    // Populate the RPC fields from the auto-detected daemon config so they show the REAL connection
    // (host/port/user/pass) instead of compile-time defaults. These are read-only in the UI: the RPC
    // credentials come from the daemon's DRAGONX.conf, so this is an accurate display, not an editor.
    const auto rpcCfg = rpc::Connection::autoDetectConfig();
    snprintf(s_settingsState.rpc_host,     sizeof(s_settingsState.rpc_host),     "%s", rpcCfg.host.c_str());
    snprintf(s_settingsState.rpc_port,     sizeof(s_settingsState.rpc_port),     "%s", rpcCfg.port.c_str());
    snprintf(s_settingsState.rpc_user,     sizeof(s_settingsState.rpc_user),     "%s", rpcCfg.rpcuser.c_str());
    snprintf(s_settingsState.rpc_password, sizeof(s_settingsState.rpc_password), "%s", rpcCfg.rpcpassword.c_str());
    s_settingsState.rpc_plaintext_remote = rpc::Connection::usesPlaintextRemote(rpcCfg);

    // Apply loaded visual effects settings
    effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount);
    effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity);
    effects::ImGuiAcrylic::SetNoiseOpacity(s_settingsState.noise_opacity);
    effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled);

    s_settingsState.initialized = true;
}

static void saveSettingsPageState(config::Settings* settings) {
    if (!settings) return;

    settings->setTheme(settings->getSkinId());
    settings->setSaveZtxs(s_settingsState.save_ztxs);
    settings->setAllowCustomFees(s_settingsState.allow_custom_fees);
    settings->setAutoShield(s_settingsState.auto_shield);
    settings->setFetchPrices(s_settingsState.fetch_prices);
    settings->setUseTor(s_settingsState.use_tor);
    settings->setTxExplorerUrl(s_settingsState.tx_explorer);
    settings->setAddressExplorerUrl(s_settingsState.addr_explorer);

    auto& i18n = util::I18n::instance();
    const auto& languages = i18n.getAvailableLanguages();
    auto it = languages.begin();
    std::advance(it, s_settingsState.language_index);
    if (it != languages.end()) {
        settings->setLanguage(it->first);
    }

    // Visual effects settings
    settings->setAcrylicEnabled(s_settingsState.acrylic_enabled);
    settings->setAcrylicQuality(s_settingsState.blur_amount > 0.001f ? static_cast<int>(effects::AcrylicQuality::Low) : static_cast<int>(effects::AcrylicQuality::Off));
    settings->setBlurMultiplier(s_settingsState.blur_amount);
    settings->setUIOpacity(s_settingsState.ui_opacity);
    settings->setWindowOpacity(s_settingsState.window_opacity);
    settings->setNoiseOpacity(s_settingsState.noise_opacity);
    settings->setGradientBackground(s_settingsState.gradient_background);
    settings->setScanlineEnabled(s_settingsState.scanline_enabled);
    settings->setThemeEffectsEnabled(s_settingsState.theme_effects_enabled);
    settings->setLowSpecMode(s_settingsState.low_spec_mode);
    settings->setReduceMotion(s_settingsState.reduce_motion);
    settings->setFontScale(s_settingsState.font_scale);
    settings->setKeepDaemonRunning(s_settingsState.keep_daemon_running);
    settings->setStopExternalDaemon(s_settingsState.stop_external_daemon);
    // Lite-server selection is owned by the Network tab; the Settings page no longer writes it.
    settings->setMineWhenIdle(s_settingsState.mine_when_idle);
    settings->setMineIdleDelay(s_settingsState.mine_idle_delay);
    settings->setIdleThreadScaling(s_settingsState.idle_thread_scaling);
    settings->setIdleThreadsActive(s_settingsState.idle_threads_active);
    settings->setIdleThreadsIdle(s_settingsState.idle_threads_idle);
    settings->setVerboseLogging(s_settingsState.verbose_logging);
    settings->setDebugCategories(s_settingsState.debug_categories);

    settings->save();
}

// Console output color toggles (accent bars + per-channel text color), shown on their own row under
// the effects checkboxes. They mirror the console toolbar buttons and carry no GPU cost, so — unlike
// scanline/theme-effects — they stay enabled in low-spec: the caller has an open
// BeginDisabled(low_spec) around the effects row, so close it, draw these live-bound to the ConsoleTab
// statics, then reopen it. Persisted immediately (the startup restore in App loads them on launch).
static void renderConsoleColorToggles(App* app) {
    ImGui::EndDisabled();
    bool accents = ConsoleTab::s_line_accents_enabled;
    if (ImGui::Checkbox(TrId("console_accents", "con_accents").c_str(), &accents)) {
        ConsoleTab::s_line_accents_enabled = accents;
        app->settings()->setConsoleLineAccents(accents);
        app->settings()->save();
    }
    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("console_toggle_accents"));
    ImGui::SameLine(0, Layout::spacingLg());
    bool textColor = ConsoleTab::s_line_text_color_enabled;
    if (ImGui::Checkbox(TrId("console_text_colors", "con_textcol").c_str(), &textColor)) {
        ConsoleTab::s_line_text_color_enabled = textColor;
        app->settings()->setConsoleTextColor(textColor);
        app->settings()->save();
    }
    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("console_toggle_text_color"));
    ImGui::BeginDisabled(s_settingsState.low_spec_mode);
}

// ============================================================================
// Settings Page Renderer
// ============================================================================

void RenderSettingsPage(App* app) {
    // Load settings state on first render
    if (!s_settingsState.initialized && app->settings()) {
        loadSettingsPageState(app->settings());
    }

    // Sync low-spec / theme-effects state from runtime each frame
    // so that hotkey toggles are reflected in the checkboxes.
    {
        bool runtimeLowSpec = effects::isLowSpecMode();
        if (s_settingsState.low_spec_mode != runtimeLowSpec) {
            if (runtimeLowSpec) {
                // Hotkey turned low-spec ON — save snapshot, override statics
                enterLowSpec(false);
            } else if (s_settingsState.low_spec_snapshot.valid) {
                // Hotkey turned low-spec OFF — restore snapshot
                exitLowSpec(false);
            } else if (app->settings()) {
                // No snapshot — read prefs from settings file
                s_settingsState.blur_amount = app->settings()->getBlurMultiplier();
                s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
                s_settingsState.ui_opacity = app->settings()->getUIOpacity();
                s_settingsState.window_opacity = app->settings()->getWindowOpacity();
                s_settingsState.theme_effects_enabled = app->settings()->getThemeEffectsEnabled();
                s_settingsState.scanline_enabled = app->settings()->getScanlineEnabled();
            }
            s_settingsState.low_spec_mode = runtimeLowSpec;
        }
        bool runtimeThemeEffects = effects::ThemeEffects::instance().isEnabled();
        if (s_settingsState.theme_effects_enabled != runtimeThemeEffects) {
            s_settingsState.theme_effects_enabled = runtimeThemeEffects;
        }
    }

    auto& S = schema::UI();

    // Responsive layout — matches other tabs
    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float scrollbarMargin = ImGui::GetStyle().ScrollbarSize + Layout::spacingSm();
    float availWidth = contentAvail.x - scrollbarMargin;
    float hs = Layout::hScale(availWidth);
    float vs = Layout::vScale(contentAvail.y);
    float pad = Layout::cardInnerPadding();
    float bottomPad = std::max(0.0f, pad - ImGui::GetStyle().ItemSpacing.y);
    float gap = Layout::cardGap();
    float glassRound = Layout::glassRounding();

    char buf[256];

    // Label column position — adaptive to width
    float labelW = std::max(S.drawElement("components.settings-page", "label-min-width").size, S.drawElement("components.settings-page", "label-width").size * hs);
    // Widen labelW if any translated label is wider (prevents overflow)
    {
        ImFont* mf = Type().body2();
        const char* labelKeys[] = {"theme", "balance_layout", "language", "acrylic", "noise",
                                    "ui_opacity", "window_opacity", "font_scale",
                                    "rpc_host", "rpc_port", "rpc_user", "rpc_pass",
                                    "transaction_url", "address_url"};
        for (const char* k : labelKeys) {
            float tw = mf->CalcTextSizeA(mf->LegacySize, FLT_MAX, 0, TR(k)).x + Layout::spacingMd();
            if (tw > labelW) labelW = tw;
        }
    }
    // Input field width — fill remaining space in card
    float inputW = std::max(S.drawElement("components.settings-page", "input-min-width").size, availWidth - labelW - pad * 2);

    // Scrollable content area — NoBackground matches other tabs

    ImGui::BeginChild("##SettingsPageScroll", ImVec2(0, 0), false,
        ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollWithMouse);
    ApplySmoothScroll();

    // Capture the ACTUAL clip boundaries from inside the child window
    ImVec2 childClipMin = ImGui::GetWindowPos();
    ImVec2 childClipMax(childClipMin.x + ImGui::GetWindowSize().x,
                        childClipMin.y + ImGui::GetWindowSize().y);
    const float dp = Layout::dpiScale();
    const float fadeH = schema::UI().drawElement("components.settings-page", "edge-fade-zone").size * dp;
    const float fadeOffTop = schema::UI().drawElement("components.settings-page", "edge-fade-offset-top").size * dp;
    const float fadeOffBot = schema::UI().drawElement("components.settings-page", "edge-fade-offset-bottom").size * dp;

    // Get draw list AFTER BeginChild so we draw on the child window's list
    ImDrawList* dl = ImGui::GetWindowDrawList();
    (void)dl; // used by cards below

    // Capture ForegroundDrawList vertex start — DrawGlassPanel draws
    // theme effects (rainbow border, shimmer, specular glare, edge trace)
    // on the ForegroundDrawList; those bypass the shader fade so we'll
    // apply a vertex-based alpha fade to them after rendering.
    ImDrawList* fgDL = ImGui::GetForegroundDrawList();
    int fgVtxStart = fgDL->VtxBuffer.Size;

    // --- Shader-based scroll fade: bind custom fragment shader ---
    // The shader multiplies output alpha by a smoothstep gradient based
    // on screen Y, giving a true per-pixel alpha mask at scroll edges.
    float settingsScrollY_pre    = ImGui::GetScrollY();
    float settingsScrollMaxY_pre = ImGui::GetScrollMaxY();
    float settingsFadeTopY    = childClipMin.y + fadeOffTop;
    float settingsFadeBottomY = childClipMax.y - fadeOffBot;
    float settingsFadeZoneTop    = (settingsScrollY_pre > 1.0f) ? fadeH : 0.0f;
    float settingsFadeZoneBot    = (settingsScrollMaxY_pre > 0 && settingsScrollY_pre < settingsScrollMaxY_pre - 1.0f) ? fadeH : 0.0f;
    if (fadeH > 0.0f && !s_settingsState.low_spec_mode && s_settingsState.fade_shader.init()) {
        s_settingsState.fade_shader.fadeTopY       = settingsFadeTopY;
        s_settingsState.fade_shader.fadeBottomY    = settingsFadeBottomY;
        s_settingsState.fade_shader.fadeZoneTop    = settingsFadeZoneTop;
        s_settingsState.fade_shader.fadeZoneBottom = settingsFadeZoneBot;
        s_settingsState.fade_shader.addBind(dl);
    }

    // Top margin from schema
    float topMargin = schema::UI().drawElement("components.settings-page", "top-margin").size;
    if (topMargin > 0.0f)
        ImGui::Dummy(ImVec2(0, topMargin));

    GlassPanelSpec glassSpec;
    glassSpec.rounding = glassRound;
    ImFont* capFont = Type().caption();
    ImFont* body2   = Type().body2();
    ImFont* sub1    = Type().subtitle1();

    // ====================================================================
    // THEME & LANGUAGE — card (draw-first approach; avoids ChannelsSplit
    //   which breaks BeginCombo popup rendering in some ImGui versions)
    // ====================================================================
    {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("theme_language"));
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

        material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);

        float contentW = availWidth - pad * 2;
        float comboGap = S.drawElement("components.settings-page", "combo-row-gap").size;
        float compactBP = S.drawElement("components.settings-page", "compact-breakpoint").size;
        bool wideLayout = availWidth >= compactBP;
        float refreshBtnW = S.drawElement("components.settings-page", "refresh-btn-width").size;

        // --- Skin data ---
        auto& skinMgr = schema::SkinManager::instance();
        const auto& skins = skinMgr.available();
        std::string active_preview = "DragonX";
        bool active_is_custom = false;
        for (const auto& skin : skins) {
            if (skin.id == skinMgr.activeSkinId()) {
                active_preview = skin.name;
                active_is_custom = !skin.bundled;
                break;
            }
        }

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

        // --- Balance layout data ---
        const auto& layouts = GetBalanceLayouts();
        std::string balPreview = s_settingsState.balance_layout;
        for (const auto& l : layouts) {
            if (l.id == s_settingsState.balance_layout) { balPreview = l.name; break; }
        }

        // --- Theme combo popup (shared between wide and narrow paths) ---
        auto renderThemeComboPopup = [&]() {
            ImGui::TextDisabled("%s", TR("settings_builtin"));
            ImGui::Separator();
            for (size_t i = 0; i < skins.size(); i++) {
                const auto& skin = skins[i];
                if (!skin.bundled) continue;
                bool is_selected = (skin.id == skinMgr.activeSkinId());
                if (ImGui::Selectable(skin.name.c_str(), is_selected)) {
                    skinMgr.setActiveSkin(skin.id);
                    if (app->settings()) {
                        app->settings()->setSkinId(skin.id);
                        app->settings()->save();
                    }
                }
                if (is_selected) ImGui::SetItemDefaultFocus();
            }
            bool has_custom = false;
            for (const auto& skin : skins) {
                if (!skin.bundled) { has_custom = true; break; }
            }
            if (has_custom) {
                ImGui::Spacing();
                ImGui::TextDisabled("%s", TR("settings_custom"));
                ImGui::Separator();
                for (size_t i = 0; i < skins.size(); i++) {
                    const auto& skin = skins[i];
                    if (skin.bundled) continue;
                    bool is_selected = (skin.id == skinMgr.activeSkinId());
                    if (!skin.valid) {
                        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
                        ImGui::BeginDisabled(true);
                        std::string lbl = skin.name + " (invalid)";
                        ImGui::Selectable(lbl.c_str(), false);
                        ImGui::EndDisabled();
                        ImGui::PopStyleColor();
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
                            material::Tooltip("%s", skin.validationError.c_str());
                    } else {
                        std::string lbl = skin.name;
                        if (!skin.author.empty()) lbl += "  (" + skin.author + ")";
                        if (ImGui::Selectable(lbl.c_str(), is_selected)) {
                            skinMgr.setActiveSkin(skin.id);
                            if (app->settings()) {
                                app->settings()->setSkinId(skin.id);
                                app->settings()->save();
                            }
                        }
                        if (is_selected) ImGui::SetItemDefaultFocus();
                    }
                }
            }
        };

        if (wideLayout) {
            // ============================================================
            // Wide: 3 combos on one row + compact 3-column effects grid
            // ============================================================

            // --- Combo row: Theme | Layout | Language  [Refresh] ---
            {
                ImGui::PushFont(body2);
                float lblGap = Layout::spacingXs();
                float lblThemeW = ImGui::CalcTextSize(TR("theme")).x + lblGap;
                float lblLayoutW = ImGui::CalcTextSize(TR("balance_layout")).x + lblGap;
                float lblLangW = ImGui::CalcTextSize(TR("language")).x + lblGap;
                float totalFixed = lblThemeW + lblLayoutW + lblLangW
                                 + comboGap * 2 + Layout::spacingSm() + refreshBtnW;
                float comboW = std::max(80.0f, (contentW - totalFixed) / 3.0f);

                ImGui::AlignTextToFramePadding();
                ImGui::TextUnformatted(TR("theme"));
                ImGui::SameLine(0, lblGap);
                ImGui::SetNextItemWidth(comboW);
                if (ImGui::BeginCombo("##Theme", active_preview.c_str())) {
                    renderThemeComboPopup();
                    ImGui::EndCombo();
                }
                if (ImGui::IsItemHovered())
                    material::Tooltip("%s", TR("tt_theme_hotkey"));

                ImGui::SameLine(0, comboGap);
                ImGui::AlignTextToFramePadding();
                ImGui::TextUnformatted(TR("balance_layout"));
                ImGui::SameLine(0, lblGap);
                ImGui::SetNextItemWidth(comboW);
                if (ImGui::BeginCombo("##BalanceLayout", balPreview.c_str())) {
                    for (const auto& l : layouts) {
                        if (!l.enabled) continue;
                        bool selected = (l.id == s_settingsState.balance_layout);
                        if (ImGui::Selectable(l.name.c_str(), selected)) {
                            s_settingsState.balance_layout = l.id;
                            if (app->settings()) {
                                app->settings()->setBalanceLayout(s_settingsState.balance_layout);
                                app->settings()->save();
                            }
                        }
                        if (selected) ImGui::SetItemDefaultFocus();
                    }
                    ImGui::EndCombo();
                }
                if (ImGui::IsItemHovered())
                    material::Tooltip("%s", TR("tt_layout_hotkey"));

                ImGui::SameLine(0, comboGap);
                ImGui::AlignTextToFramePadding();
                ImGui::TextUnformatted(TR("language"));
                ImGui::SameLine(0, lblGap);
                ImGui::SetNextItemWidth(comboW);
                if (ImGui::Combo("##Language", &s_settingsState.language_index, lang_names.data(),
                                 static_cast<int>(lang_names.size()))) {
                    auto it = languages.begin();
                    std::advance(it, s_settingsState.language_index);
                    i18n.loadLanguage(it->first);
                    if (app->settings()) {
                        app->settings()->setLanguage(it->first);
                        app->settings()->save();
                    }
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_language"));

                ImGui::SameLine(0, Layout::spacingSm());
                if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) {
                    schema::SkinManager::instance().refresh();
                    Notifications::instance().info("Theme list refreshed");
                }
                if (ImGui::IsItemHovered()) {
                    material::Tooltip(TR("tt_scan_themes"),
                        schema::SkinManager::getUserSkinsDirectory().c_str());
                }
                ImGui::PopFont();
            }

            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

            // --- Font Scale slider (always visible) ---
            {
                ImGui::PushFont(body2);
                ImGui::TextUnformatted(TR("font_scale"));
                float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, contentW);
                ImGui::SetNextItemWidth(fontSliderW);
                s_settingsState.font_scale = Layout::userFontScale();
                float prev_font_scale = s_settingsState.font_scale;
                {
                    char fs_fmt[16];
                    snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", s_settingsState.font_scale);
                    ImGui::SliderFloat("##FontScale", &s_settingsState.font_scale, 1.0f, 1.5f, fs_fmt,
                                       ImGuiSliderFlags_AlwaysClamp);
                }
                s_settingsState.font_scale = std::max(1.0f, std::min(1.5f,
                    std::round(s_settingsState.font_scale * 20.0f) / 20.0f));
                if (s_settingsState.font_scale != prev_font_scale)
                    Layout::setUserFontScaleVisual(s_settingsState.font_scale);
                if (ImGui::IsItemDeactivatedAfterEdit()) {
                    Layout::setUserFontScale(s_settingsState.font_scale);
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_font_scale"));
                ImGui::PopFont();
            }

            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

            // --- Collapsible: Advanced Effects... ---
            material::CollapsibleHeader(dl, "##EffectsToggle", TR("advanced_effects"),
                                        s_settingsState.effects_expanded, contentW,
                                        body2, OnSurfaceMedium());

            if (s_settingsState.effects_expanded) {
                ImGui::PushFont(body2);

                // Checkbox row: Low-spec | Console scanline | Theme effects | Gradient background
                if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &s_settingsState.low_spec_mode)) {
                    effects::setLowSpecMode(s_settingsState.low_spec_mode);
                    if (s_settingsState.low_spec_mode) {
                        enterLowSpec(true);
                    } else if (s_settingsState.low_spec_snapshot.valid) {
                        exitLowSpec(true);
                    }
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_low_spec"));

                ImGui::SameLine(0, Layout::spacingLg());
                if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &s_settingsState.gradient_background)) {
                    schema::SkinManager::instance().setGradientMode(s_settingsState.gradient_background);
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_simple_bg"));

                ImGui::SameLine(0, Layout::spacingLg());
                if (ImGui::Checkbox(TrId("reduce_motion", "reduce_motion").c_str(), &s_settingsState.reduce_motion)) {
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_reduce_motion"));

                ImGui::BeginDisabled(s_settingsState.low_spec_mode);

                ImGui::SameLine(0, Layout::spacingLg());
                if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &s_settingsState.scanline_enabled)) {
                    ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled;
                    app->settings()->setScanlineEnabled(s_settingsState.scanline_enabled);
                    app->settings()->save();
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_scanline"));

                ImGui::SameLine(0, Layout::spacingLg());
                if (ImGui::Checkbox(TrId("theme_effects", "effects").c_str(), &s_settingsState.theme_effects_enabled)) {
                    effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled);
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_theme_effects"));

                ImGui::SameLine(0, Layout::spacingLg());
                {
                    bool anim = app->settings()->getAnimateAvatars();
                    if (ImGui::Checkbox(TrId("animate_avatars", "animate_avatars").c_str(), &anim)) {
                        app->settings()->setAnimateAvatars(anim);
                        app->settings()->save();
                    }
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_animate_avatars"));
                }

                // Console output color toggles (own row — no GPU cost, enabled even in low-spec).
                renderConsoleColorToggles(app);

                // Row 1: Acrylic preset slider + Noise slider (side by side, labels above)
                float effCtrlMinW = S.drawElement("components.settings-page", "effects-input-min-width").size;
                float halfW = (contentW - Layout::spacingLg()) * 0.5f;
                float ctrlW = std::max(effCtrlMinW, halfW);
                float baseX = ImGui::GetCursorScreenPos().x;
                float rightX = baseX + ctrlW + Layout::spacingLg();

                ImGui::TextUnformatted(TR("acrylic"));
                float row1Y = ImGui::GetCursorScreenPos().y;
                ImGui::SetNextItemWidth(ctrlW);
                {
                    char blur_fmt[16];
                    if (s_settingsState.blur_amount < 0.01f)
                        snprintf(blur_fmt, sizeof(blur_fmt), "%s", TR("slider_off"));
                    else
                        snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", s_settingsState.blur_amount / kAcrylicMaxBlur * 100.0f);
                    if (ImGui::SliderFloat("##AcrylicBlur", &s_settingsState.blur_amount, 0.0f, kAcrylicMaxBlur, blur_fmt,
                                           ImGuiSliderFlags_AlwaysClamp)) {
                        if (s_settingsState.blur_amount > 0.0f && s_settingsState.blur_amount < kAcrylicMaxBlur * 0.04f) s_settingsState.blur_amount = 0.0f;
                        s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
                        effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount);
                    }
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_blur"));
                float afterRow1Y = ImGui::GetCursorScreenPos().y;

                float lblH = ImGui::GetTextLineHeight() + ImGui::GetStyle().ItemSpacing.y;
                ImGui::SetCursorScreenPos(ImVec2(rightX, row1Y - lblH));
                ImGui::TextUnformatted(TR("noise"));
                ImGui::SetCursorScreenPos(ImVec2(rightX, row1Y));
                ImGui::SetNextItemWidth(ctrlW);
                {
                    char noise_fmt[16];
                    if (s_settingsState.noise_opacity < 0.01f)
                        snprintf(noise_fmt, sizeof(noise_fmt), "%s", TR("slider_off"));
                    else
                        snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", s_settingsState.noise_opacity * 100.0f);
                    if (ImGui::SliderFloat("##NoiseOpacity", &s_settingsState.noise_opacity, 0.0f, 1.0f, noise_fmt,
                                           ImGuiSliderFlags_AlwaysClamp)) {
                        effects::ImGuiAcrylic::SetNoiseOpacity(s_settingsState.noise_opacity);
                        saveSettingsPageState(app->settings());
                    }
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_noise"));

                ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow1Y));

                ImGui::TextUnformatted(TR("ui_opacity"));
                float row2Y = ImGui::GetCursorScreenPos().y;
                ImGui::SetNextItemWidth(ctrlW);
                {
                    char uiop_fmt[16];
                    snprintf(uiop_fmt, sizeof(uiop_fmt), "%.0f%%%%", s_settingsState.ui_opacity * 100.0f);
                    if (ImGui::SliderFloat("##UIOpacity", &s_settingsState.ui_opacity, 0.3f, 1.0f, uiop_fmt,
                                           ImGuiSliderFlags_AlwaysClamp)) {
                        effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity);
                        saveSettingsPageState(app->settings());
                    }
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_ui_opacity"));
                float afterRow2Y = ImGui::GetCursorScreenPos().y;

                ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y - lblH));
                ImGui::TextUnformatted(TR("window_opacity"));
                ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y));
                ImGui::SetNextItemWidth(ctrlW);
                {
                    char winop_fmt[16];
                    snprintf(winop_fmt, sizeof(winop_fmt), "%.0f%%%%", s_settingsState.window_opacity * 100.0f);
                    if (ImGui::SliderFloat("##WindowOpacity", &s_settingsState.window_opacity, 0.3f, 1.0f, winop_fmt,
                                           ImGuiSliderFlags_AlwaysClamp)) {
                        saveSettingsPageState(app->settings());
                    }
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_window_opacity"));

                ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow2Y));

                ImGui::EndDisabled(); // low-spec
                ImGui::PopFont();
            } // s_settingsState.effects_expanded
        } else {
            // ============================================================
            // Narrow: stacked combos + 2-column effects (original layout)
            // ============================================================

            // --- Theme row ---
            {
                ImGui::PushFont(body2);
                ImGui::AlignTextToFramePadding();
                ImGui::TextUnformatted(TR("theme"));
                ImGui::SameLine(labelW);

                float themeComboW = std::max(S.drawElement("components.settings-page", "theme-combo-min-width").size,
                    availWidth - pad * 2 - labelW - refreshBtnW - Layout::spacingSm());
                ImGui::SetNextItemWidth(themeComboW);
                if (ImGui::BeginCombo("##Theme", active_preview.c_str())) {
                    renderThemeComboPopup();
                    ImGui::EndCombo();
                }
                if (ImGui::IsItemHovered())
                    material::Tooltip("%s", TR("tt_theme_hotkey"));
                if (active_is_custom) {
                    ImGui::SameLine();
                    ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "*");
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_custom_theme"));
                }
                ImGui::SameLine();
                if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) {
                    schema::SkinManager::instance().refresh();
                    Notifications::instance().info("Theme list refreshed");
                }
                if (ImGui::IsItemHovered()) {
                    material::Tooltip(TR("tt_scan_themes"),
                        schema::SkinManager::getUserSkinsDirectory().c_str());
                }
                ImGui::PopFont();
            }

            ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

            // --- Balance Layout row ---
            {
                ImGui::PushFont(body2);
                ImGui::AlignTextToFramePadding();
                ImGui::TextUnformatted(TR("balance_layout"));
                ImGui::SameLine(labelW);
                ImGui::SetNextItemWidth(std::max(180.0f, inputW));
                if (ImGui::BeginCombo("##BalanceLayout", balPreview.c_str())) {
                    for (const auto& l : layouts) {
                        if (!l.enabled) continue;
                        bool selected = (l.id == s_settingsState.balance_layout);
                        if (ImGui::Selectable(l.name.c_str(), selected)) {
                            s_settingsState.balance_layout = l.id;
                            if (app->settings()) {
                                app->settings()->setBalanceLayout(s_settingsState.balance_layout);
                                app->settings()->save();
                            }
                        }
                        if (selected) ImGui::SetItemDefaultFocus();
                    }
                    ImGui::EndCombo();
                }
                if (ImGui::IsItemHovered())
                    material::Tooltip("%s", TR("tt_layout_hotkey"));
                ImGui::PopFont();
            }

            ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

            // --- Language row ---
            {
                ImGui::PushFont(body2);
                ImGui::AlignTextToFramePadding();
                ImGui::TextUnformatted(TR("language"));
                ImGui::SameLine(labelW);
                ImGui::SetNextItemWidth(inputW);
                if (ImGui::Combo("##Language", &s_settingsState.language_index, lang_names.data(),
                                 static_cast<int>(lang_names.size()))) {
                    auto it = languages.begin();
                    std::advance(it, s_settingsState.language_index);
                    i18n.loadLanguage(it->first);
                    if (app->settings()) {
                        app->settings()->setLanguage(it->first);
                        app->settings()->save();
                    }
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_language"));
                ImGui::PopFont();
            }

            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

            // --- Font Scale slider (always visible) ---
            {
                ImGui::PushFont(body2);
                ImGui::TextUnformatted(TR("font_scale"));
                float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
                    availWidth - pad * 2);
                ImGui::SetNextItemWidth(fontSliderW);
                s_settingsState.font_scale = Layout::userFontScale();
                float prev_font_scale = s_settingsState.font_scale;
                {
                    char fs_fmt[16];
                    snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", s_settingsState.font_scale);
                    ImGui::SliderFloat("##FontScale", &s_settingsState.font_scale, 1.0f, 1.5f, fs_fmt,
                                       ImGuiSliderFlags_AlwaysClamp);
                }
                s_settingsState.font_scale = std::max(1.0f, std::min(1.5f,
                    std::round(s_settingsState.font_scale * 20.0f) / 20.0f));
                if (s_settingsState.font_scale != prev_font_scale)
                    Layout::setUserFontScaleVisual(s_settingsState.font_scale);
                if (ImGui::IsItemDeactivatedAfterEdit()) {
                    Layout::setUserFontScale(s_settingsState.font_scale);
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_font_scale"));
                ImGui::PopFont();
            }

            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

            // --- Collapsible: Advanced Effects... ---
            {
                float narrowContentW = availWidth - pad * 2;
                material::CollapsibleHeader(dl, "##EffectsToggleN", TR("advanced_effects"),
                                            s_settingsState.effects_expanded, narrowContentW,
                                            body2, OnSurfaceMedium());
            }

            if (s_settingsState.effects_expanded) {
                ImGui::PushFont(body2);

                if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &s_settingsState.low_spec_mode)) {
                    effects::setLowSpecMode(s_settingsState.low_spec_mode);
                    if (s_settingsState.low_spec_mode) {
                        enterLowSpec(true);
                    } else if (s_settingsState.low_spec_snapshot.valid) {
                        exitLowSpec(true);
                    }
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_low_spec"));

                if (ImGui::Checkbox(TrId("settings_gradient_bg", "gradient_bg").c_str(), &s_settingsState.gradient_background)) {
                    schema::SkinManager::instance().setGradientMode(s_settingsState.gradient_background);
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_simple_bg_alt"));

                if (ImGui::Checkbox(TrId("reduce_motion", "reduce_motion").c_str(), &s_settingsState.reduce_motion)) {
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_reduce_motion"));

                ImGui::BeginDisabled(s_settingsState.low_spec_mode);

                if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &s_settingsState.scanline_enabled)) {
                    ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled;
                    app->settings()->setScanlineEnabled(s_settingsState.scanline_enabled);
                    app->settings()->save();
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_scanline"));

                ImGui::SameLine(0, Layout::spacingLg());
                if (ImGui::Checkbox(TrId("theme_effects", "theme_fx").c_str(), &s_settingsState.theme_effects_enabled)) {
                    effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled);
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_theme_effects"));

                ImGui::SameLine(0, Layout::spacingLg());
                {
                    bool anim = app->settings()->getAnimateAvatars();
                    if (ImGui::Checkbox(TrId("animate_avatars", "animate_avatars").c_str(), &anim)) {
                        app->settings()->setAnimateAvatars(anim);
                        app->settings()->save();
                    }
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_animate_avatars"));
                }

                // Console output color toggles (own row — no GPU cost, enabled even in low-spec).
                renderConsoleColorToggles(app);

                float ctrlW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
                    availWidth - pad * 2.0f);
                ImGui::TextUnformatted(TR("acrylic"));
                ImGui::SetNextItemWidth(ctrlW);
                {
                    char blur_fmt[16];
                    if (s_settingsState.blur_amount < 0.01f)
                        snprintf(blur_fmt, sizeof(blur_fmt), "%s", TR("slider_off"));
                    else
                        snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", s_settingsState.blur_amount / kAcrylicMaxBlur * 100.0f);
                    if (ImGui::SliderFloat("##AcrylicBlur", &s_settingsState.blur_amount, 0.0f, kAcrylicMaxBlur, blur_fmt,
                                           ImGuiSliderFlags_AlwaysClamp)) {
                        if (s_settingsState.blur_amount > 0.0f && s_settingsState.blur_amount < kAcrylicMaxBlur * 0.04f) s_settingsState.blur_amount = 0.0f;
                        s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
                        effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount);
                    }
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_blur"));

                ImGui::TextUnformatted(TR("noise"));
                ImGui::SetNextItemWidth(ctrlW);
                {
                    char noise_fmt[16];
                    if (s_settingsState.noise_opacity < 0.01f)
                        snprintf(noise_fmt, sizeof(noise_fmt), "%s", TR("slider_off"));
                    else
                        snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", s_settingsState.noise_opacity * 100.0f);
                    if (ImGui::SliderFloat("##NoiseOpacity", &s_settingsState.noise_opacity, 0.0f, 1.0f, noise_fmt,
                                           ImGuiSliderFlags_AlwaysClamp)) {
                        effects::ImGuiAcrylic::SetNoiseOpacity(s_settingsState.noise_opacity);
                    }
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_noise"));

                ImGui::TextUnformatted(TR("ui_opacity"));
                ImGui::SetNextItemWidth(ctrlW);
                {
                    char uiop_fmt[16];
                    snprintf(uiop_fmt, sizeof(uiop_fmt), "%.0f%%%%", s_settingsState.ui_opacity * 100.0f);
                    if (ImGui::SliderFloat("##UIOpacity", &s_settingsState.ui_opacity, 0.3f, 1.0f, uiop_fmt,
                                           ImGuiSliderFlags_AlwaysClamp)) {
                        effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity);
                    }
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_ui_opacity"));

                ImGui::TextUnformatted(TR("window_opacity"));
                ImGui::SetNextItemWidth(ctrlW);
                {
                    char winop_fmt[16];
                    snprintf(winop_fmt, sizeof(winop_fmt), "%.0f%%%%", s_settingsState.window_opacity * 100.0f);
                    if (ImGui::SliderFloat("##WindowOpacity", &s_settingsState.window_opacity, 0.3f, 1.0f, winop_fmt,
                                           ImGuiSliderFlags_AlwaysClamp)) {
                    }
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_window_opacity"));

                ImGui::EndDisabled(); // low-spec
                ImGui::PopFont();
            } // s_settingsState.effects_expanded
        }
    }

    ImGui::Dummy(ImVec2(0, gap));

    // ====================================================================
    // WALLET — card (privacy/daemon toggles + collapsible tools)
    // ====================================================================
    {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet"));
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

        material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
        const ImVec2& cardMin = card.cardMin;

        float contentW = availWidth - pad * 2;

        // Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
        {
            const bool showDaemonOptions = app->supportsFullNodeLifecycleActions();
            float cbSpacing = Layout::spacingMd();
            float fh = ImGui::GetFrameHeight();
            float inner = ImGui::GetStyle().ItemInnerSpacing.x;
            auto cbW = [&](const char* label) { return fh + inner + ImGui::CalcTextSize(label).x; };

            float totalW = cbW(TR("save_z_transactions")) + cbSpacing
                         + cbW(TR("auto_shield")) + cbSpacing
                         + cbW(TR("use_tor")) + cbSpacing;
            if (showDaemonOptions) {
                totalW += cbW(TR("keep_daemon")) + cbSpacing
                       + cbW(TR("stop_external")) + cbSpacing;
            }
            totalW += cbW(TR("verbose_logging"));
            float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
            if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
            float sp = cbSpacing * scale;

            ImGui::Checkbox(TrId("save_z_transactions", "save_ztx").c_str(), &s_settingsState.save_ztxs);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_save_ztx"));
            ImGui::SameLine(0, sp);
            ImGui::Checkbox(TrId("auto_shield", "auto_shld").c_str(), &s_settingsState.auto_shield);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_auto_shield"));
            ImGui::SameLine(0, sp);
            ImGui::Checkbox(TrId("use_tor", "tor").c_str(), &s_settingsState.use_tor);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_tor"));
            if (showDaemonOptions) {
                ImGui::SameLine(0, sp);
                if (ImGui::Checkbox(TrId("keep_daemon", "keep_dmn").c_str(), &s_settingsState.keep_daemon_running)) {
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered())
                    material::Tooltip("%s", TR("tt_keep_daemon"));
                ImGui::SameLine(0, sp);
                if (ImGui::Checkbox(TrId("stop_external", "stop_ext").c_str(), &s_settingsState.stop_external_daemon)) {
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered())
                    material::Tooltip("%s", TR("tt_stop_external"));
            }
            ImGui::SameLine(0, sp);
            if (ImGui::Checkbox(TrId("verbose_logging", "verbose").c_str(), &s_settingsState.verbose_logging)) {
                dragonx::util::Logger::instance().setVerbose(s_settingsState.verbose_logging);
                saveSettingsPageState(app->settings());
            }
            if (ImGui::IsItemHovered())
                material::Tooltip("%s", TR("tt_verbose"));

            if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
        }

        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

        // --- Collapsible: Tools & Actions... ---
        material::CollapsibleHeader(dl, "##ToolsToggle", TR("tools_actions"),
                                    s_settingsState.tools_expanded, contentW,
                                    body2, OnSurfaceMedium());

        if (s_settingsState.tools_expanded) {
            float btnSpacing = Layout::spacingMd();
            int btnsPerRow = (contentW >= 600.0f) ? 3 : 2;
            float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow;
            float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f);
            bw = std::max(minBtnW, bw);

            if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button")))
                app->setCurrentPage(ui::NavPage::Contacts);  // now a top-level tab
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_address_book"));
            ImGui::SameLine(0, btnSpacing);
            if (TactileButton(TR("settings_validate_address"), ImVec2(bw, 0), S.resolveFont("button")))
                ValidateAddressDialog::show();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_validate"));
            if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
            if (TactileButton(TR("settings_request_payment"), ImVec2(bw, 0), S.resolveFont("button")))
                RequestPaymentDialog::show();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_request_payment"));
            if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); }
            if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button")))
                ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_shield_mining"));
            ImGui::SameLine(0, btnSpacing);
            if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button")))
                ShieldDialog::show(ShieldDialog::Mode::MergeToAddress);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_merge"));
            if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
            if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) {
                s_settingsState.confirm_clear_ztx = true;
            }
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_clear_ztx"));
        }

        // --- Backup & Data (merged in from its former standalone card) ---
        ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("backup_data"));
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
        {
            float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
            const char* r1[] = {TR("settings_import_key"), TR("settings_export_key"), TR("settings_export_all"), TR("settings_backup"), TR("settings_export_csv")};
            const char* t1[] = { TR("tt_import_key"), TR("tt_export_key"), TR("tt_export_all"), TR("tt_backup"), TR("tt_export_csv") };
            const bool showFullNodeLifecycleActions = app->supportsFullNodeLifecycleActions();
            const char* wizLabel = TR("setup_wizard");
            const char* bsLabel = TR("download_bootstrap");
            float sp = Layout::spacingSm();
            ImFont* btnFont = S.resolveFont("button");

            float btnPadX = btnPad * 2;
            float naturalW = 0;
            for (int i = 0; i < 5; i++)
                naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
            float impViewW = ImGui::CalcTextSize(TR("settings_import_viewkey")).x + btnPadX;
            naturalW += impViewW;   // the extra "Import viewing key" button (rendered after Import key)
            float wizW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(wizLabel).x + btnPadX : 0.0f;
            float bsW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(bsLabel).x + btnPadX : 0.0f;
            // Full-node-only "Seed phrase" + "Migrate to seed" buttons trail the Backup button.
            float seedW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(TR("seed_backup_button")).x + btnPadX : 0.0f;
            float migrateW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(TR("seed_migrate_button")).x + btnPadX : 0.0f;
            float totalW = naturalW + sp * 6;   // 6 base buttons (incl. Import viewing key)
            if (showFullNodeLifecycleActions) totalW += wizW + bsW + seedW + migrateW + sp * 4;

            float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
            if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
            float scaledSp = sp * scale;

            if (TactileButton(r1[0], ImVec2(0, 0), btnFont))
                app->showImportKeyDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[0]);
            // Watch-only shielded viewing-key import (separate button — different key type + scan-height).
            ImGui::SameLine(0, scaledSp);
            if (TactileButton(TR("settings_import_viewkey"), ImVec2(0, 0), btnFont))
                app->showImportViewingKeyDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_import_viewkey"));
            ImGui::SameLine(0, scaledSp);
            if (TactileButton(r1[1], ImVec2(0, 0), btnFont))
                app->showExportKeyDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[1]);
            ImGui::SameLine(0, scaledSp);
            if (TactileButton(r1[2], ImVec2(0, 0), btnFont))
                ExportAllKeysDialog::show();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[2]);
            ImGui::SameLine(0, scaledSp);
            if (TactileButton(r1[3], ImVec2(0, 0), btnFont))
                app->showBackupDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[3]);
            if (showFullNodeLifecycleActions) {
                // Back up the wallet's BIP39 seed phrase (full-node mnemonic wallets).
                ImGui::SameLine(0, scaledSp);
                if (TactileButton(TR("seed_backup_button"), ImVec2(0, 0), btnFont))
                    app->showSeedBackupDialog();
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_backup"));
                // Migrate to a new seed-phrase wallet (create in isolation, then sweep funds). A
                // legacy (pre-seed-phrase) wallet glows the button to nudge the user toward migrating.
                ImGui::SameLine(0, scaledSp);
                const bool migrateGlow = app->isPreSeedWallet();
                if (TactileButton(TR("seed_migrate_button"), ImVec2(0, 0), btnFont))
                    app->showSeedMigrationDialog();
                if (migrateGlow) {
                    const ImVec2 gmn = ImGui::GetItemRectMin(), gmx = ImGui::GetItemRectMax();
                    const float gdp = Layout::dpiScale();
                    const float pulse = 0.5f + 0.5f * std::sin((float)ImGui::GetTime() * 3.2f);   // 0..1
                    ImDrawList* gdl = ImGui::GetWindowDrawList();
                    for (int g = 3; g >= 1; --g) {   // soft, pulsing outward halo in the accent color
                        const float e = (float)g * 2.2f * gdp;
                        const int a = (int)((70.0f + pulse * 95.0f) / (float)g);
                        gdl->AddRect(ImVec2(gmn.x - e, gmn.y - e), ImVec2(gmx.x + e, gmx.y + e),
                                     material::WithAlpha(material::Primary(), a), 6.0f * gdp + e, 0, 1.6f * gdp);
                    }
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_migrate"));
                // Multi-wallet: list wallet files + switch the active one.
                ImGui::SameLine(0, scaledSp);
                if (TactileButton(TR("wallets_button"), ImVec2(0, 0), btnFont))
                    ui::WalletsDialog::show(app);
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_wallets_button"));
            }
            ImGui::SameLine(0, scaledSp);
            if (TactileButton(r1[4], ImVec2(0, 0), btnFont))
                ExportTransactionsDialog::show();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[4]);

            if (showFullNodeLifecycleActions) {
                // Right-align Setup Wizard + Download Bootstrap.
                float framePadX2 = ImGui::GetStyle().FramePadding.x * 2.0f;
                float curX = ImGui::GetCursorScreenPos().x;
                float wizBtnW = ImGui::CalcTextSize(wizLabel).x + framePadX2;
                float bsBtnW = ImGui::CalcTextSize(bsLabel).x + framePadX2;
                float rightEdge = cardMin.x + availWidth - pad;
                float rightGroupW = bsBtnW + scaledSp + wizBtnW;
                float groupX = rightEdge - rightGroupW;
                if (groupX > curX) {
                    ImGui::SameLine(0, 0);
                    ImGui::SetCursorScreenPos(ImVec2(groupX, ImGui::GetCursorScreenPos().y));
                } else {
                    ImGui::SameLine(0, scaledSp);
                }
                if (TactileButton(bsLabel, ImVec2(0, 0), btnFont))
                    BootstrapDownloadDialog::show(app);
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_download_bootstrap"));
                ImGui::SameLine(0, scaledSp);
                if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
                    app->restartWizard();
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_wizard"));
            }

            if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
        }
    }

    ImGui::Dummy(ImVec2(0, gap));


    // ====================================================================
    // NODE & SECURITY — card
    // ====================================================================
    {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("node_security"));
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

        material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);

        float contentW = availWidth - pad * 2;
        float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(130.0f);
        float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
        auto rowBtnW = [&](std::initializer_list<const char*> labels) -> float {
            float maxTextW = 0;
            for (auto* l : labels) maxTextW = std::max(maxTextW, ImGui::CalcTextSize(l).x);
            return std::max(minBtnW, maxTextW + btnPad * 2);
        };

        // --- NODE & SECURITY: one full-width card, controls laid out horizontally ---
        // A single vertical flow of sub-sections (Node, RPC, Security, Daemon binary),
        // each with its overline header and its controls flowing left-to-right to fill
        // the card width. No 2x2 grid, no column clips, no masonry placement.
        {
            ImVec2 sectionOrigin = ImGui::GetCursorScreenPos();
            // The lite WALLET block below still positions its rows against a "left"
            // column origin/width; in the single-column layout that is simply the full
            // content column at the section origin.
            const float leftX    = sectionOrigin.x;
            const float leftColW = contentW;

            ImFont* body2Info = S.resolveFont("body2");
            if (!body2Info) body2Info = Type().body2();

            // Shared SECURITY controls, rendered full-width with horizontal rows. Used by
            // both the lite branch (auto-lock + PIN only) and the full-node branch (which
            // also gets the RPC-backed encrypt/change/lock/remove block, includeRpcEncrypt).
            // `secX` is the row start X (== content-column left); `secColW` its width.
            auto renderSecuritySection = [&](float secX, float secColW, bool includeRpcEncrypt) {
                // Encrypt / Change passphrase / Lock / Remove — RPC-backed, full-node only.
                // In lite the wallet's encryption controls live in the WALLET block above.
                if (includeRpcEncrypt) {
                    bool isEncrypted = app->state().isEncrypted();
                    bool isLocked = app->state().isLocked();

                    float secBtnW = std::min(rowBtnW({TR("settings_encrypt_wallet"), TR("settings_change_passphrase"), TR("settings_lock_now")}), (secColW - Layout::spacingMd()) * 0.5f);

                    ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
                    if (!isEncrypted) {
                        if (TactileButton(TR("settings_encrypt_wallet"), ImVec2(secBtnW, 0), S.resolveFont("button")))
                            app->showEncryptDialog();
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_encrypt"));
                        ImGui::SameLine(0, Layout::spacingMd());
                        ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_not_encrypted"));
                    } else {
                        if (TactileButton(TR("settings_change_passphrase"), ImVec2(secBtnW, 0), S.resolveFont("button")))
                            app->showChangePassphraseDialog();
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_change_pass"));
                        ImGui::SameLine(0, Layout::spacingMd());
                        if (isLocked) {
                            ImGui::PushFont(Type().iconSmall());
                            ImGui::TextColored(ImVec4(1,0.7f,0.3f,1.0f), ICON_MD_LOCK);
                            ImGui::PopFont();
                            ImGui::SameLine(0, Layout::spacingXs());
                            ImGui::TextColored(ImVec4(1,0.7f,0.3f,1.0f), "%s", TR("settings_locked"));
                        } else {
                            if (TactileButton(TR("settings_lock_now"), ImVec2(secBtnW, 0), S.resolveFont("button")))
                                app->lockWallet();
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lock"));
                            ImGui::SameLine(0, Layout::spacingSm());
                            ImGui::PushFont(Type().iconSmall());
                            ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), ICON_MD_LOCK_OPEN);
                            ImGui::PopFont();
                            ImGui::SameLine(0, Layout::spacingXs());
                            ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_unlocked"));
                        }
                        // Remove Encryption button — trails the row.
                        ImGui::SameLine(0, Layout::spacingMd());
                        if (TactileButton(TR("settings_remove_encryption"), ImVec2(secBtnW, 0), S.resolveFont("button")))
                            app->showDecryptDialog();
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_remove_encrypt"));
                    }
                }

                // Auto-Lock timeout + PIN controls — continue on the same row as the encrypt
                // controls (full-node) so the whole SECURITY band fills the width; on lite
                // (no encrypt row) they start a fresh row at the section's left edge.
                {
                    float comboW = S.drawElement("components.settings-page", "security-combo-width").sizeOr(120.0f);

                    int timeout = app->settings()->getAutoLockTimeout();
                    const char* timeoutLabels[] = { TR("timeout_off"), TR("timeout_1min"), TR("timeout_5min"), TR("timeout_15min"), TR("timeout_30min"), TR("timeout_1hour") };
                    int timeoutValues[] = { 0, 60, 300, 900, 1800, 3600 };
                    int selTimeout = 0;
                    for (int i = 0; i < 6; i++) {
                        if (timeoutValues[i] == timeout) { selTimeout = i; break; }
                    }
                    if (includeRpcEncrypt)
                        ImGui::SameLine(0, Layout::spacingLg());
                    else
                        ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
                    ImGui::AlignTextToFramePadding();
                    Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("settings_auto_lock"));
                    ImGui::SameLine(0, Layout::spacingXs());
                    ImGui::PushItemWidth(comboW);
                    if (ImGui::Combo("##autolock", &selTimeout, timeoutLabels, 6)) {
                        app->settings()->setAutoLockTimeout(timeoutValues[selTimeout]);
                        app->settings()->save();
                    }
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_auto_lock"));
                    ImGui::PopItemWidth();

                    // PIN unlock controls, trailing the auto-lock combo on the same row.
                    bool isEncryptedPIN = app->state().isEncrypted();
                    if (isEncryptedPIN) {
                        bool hasPIN = app->hasPinVault();
                        float pinBtnW = std::min(rowBtnW({TR("settings_set_pin"), TR("settings_change_pin"), TR("settings_remove_pin")}), (secColW - Layout::spacingSm()) * 0.5f);

                        ImGui::SameLine(0, Layout::spacingMd());
                        if (!hasPIN) {
                            if (TactileButton(TR("settings_set_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
                                app->showPinSetupDialog();
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_set_pin"));
                            ImGui::SameLine(0, Layout::spacingMd());
                            ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_quick_unlock_pin"));
                        } else {
                            if (TactileButton(TR("settings_change_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
                                app->showPinChangeDialog();
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_change_pin"));
                            ImGui::SameLine(0, Layout::spacingSm());
                            if (TactileButton(TR("settings_remove_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
                                app->showPinRemoveDialog();
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_remove_pin"));
                            ImGui::SameLine(0, Layout::spacingMd());
                            ImGui::PushFont(Type().iconSmall());
                            ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), ICON_MD_DIALPAD);
                            ImGui::PopFont();
                            ImGui::SameLine(0, Layout::spacingXs());
                            ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_pin_active"));
                        }
                    } else {
                        ImGui::SameLine(0, Layout::spacingMd());
                        ImGui::AlignTextToFramePadding();
                        ImGui::TextColored(ImVec4(1,1,1,0.3f), "%s", TR("settings_encrypt_first_pin"));
                    }
                }
            };

            if (app->isLiteBuild()) {
                // ============================ LITE ============================
                // Lite has no daemon: this card holds wallet lifecycle/encryption
                // (the "WALLET" block) followed by the shared SECURITY block, both
                // rendered full-width in a single column exactly as before.
                Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet"));
                ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

                {
                ImGui::PushFont(body2Info);
                {
                    float liteLabelW = std::min(leftColW * 0.35f, 132.0f);
                    float liteInputW = std::max(80.0f, leftColW - liteLabelW - Layout::spacingSm());

                    // Lite-server selection lives in the dedicated Network tab now.
                    Type().textColored(TypeStyle::Body2, OnSurfaceMedium(),
                                       TR("lite_servers_network_tab"));

                    ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                    if (material::TactileButton(TrId("lite_wallet_request", "LiteLifecycleToggle").c_str(), ImVec2(liteInputW, 0))) {
                        s_settingsState.lite_lifecycle_expanded = !s_settingsState.lite_lifecycle_expanded;
                    }

                    if (s_settingsState.lite_lifecycle_expanded) {
                        const char* lifecycleLabels[] = {TR("lite_op_create"), TR("lite_op_open"), TR("lite_op_restore")};
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        ImGui::AlignTextToFramePadding();
                        ImGui::TextUnformatted(TR("lite_action"));
                        ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
                        ImGui::SetNextItemWidth(liteInputW);
                        if (ImGui::BeginCombo("##LiteLifecycleOperation",
                                              lifecycleLabels[std::max(0, std::min(2, s_settingsState.lite_lifecycle_operation))])) {
                            for (int operationIndex = 0; operationIndex < 3; ++operationIndex) {
                                const bool selected = s_settingsState.lite_lifecycle_operation == operationIndex;
                                if (ImGui::Selectable(lifecycleLabels[operationIndex], selected)) {
                                    s_settingsState.lite_lifecycle_operation = operationIndex;
                                    s_settingsState.lite_lifecycle_status.clear();
                                    s_settingsState.lite_lifecycle_summary.clear();
                                }
                                if (selected) ImGui::SetItemDefaultFocus();
                            }
                            ImGui::EndCombo();
                        }

                        if (s_settingsState.lite_lifecycle_operation == 1 ||
                            s_settingsState.lite_lifecycle_operation == 2) {
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            ImGui::AlignTextToFramePadding();
                            ImGui::TextUnformatted(TR("lite_wallet_label"));
                            ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
                            ImGui::SetNextItemWidth(liteInputW);
                            ImGui::InputText("##LiteWalletPath", s_settingsState.lite_wallet_path,
                                             sizeof(s_settingsState.lite_wallet_path));
                        }

                        if (s_settingsState.lite_lifecycle_operation == 2) {
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            ImGui::AlignTextToFramePadding();
                            ImGui::TextUnformatted(TR("lite_seed_label"));
                            ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
                            ImGui::SetNextItemWidth(liteInputW);
                            ImGui::InputText("##LiteRestoreSeed", s_settingsState.lite_restore_seed,
                                             sizeof(s_settingsState.lite_restore_seed),
                                             ImGuiInputTextFlags_Password);
                            // Live 24-word count so the user knows the seed is complete before restoring.
                            {
                                const int wc = liteSeedWordCount(s_settingsState.lite_restore_seed);
                                char wbuf[32];
                                snprintf(wbuf, sizeof(wbuf), TR("lite_word_count"), wc);
                                ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                                Type().textColored(TypeStyle::Caption, wc == 24 ? Success() : Warning(), wbuf);
                            }

                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            ImGui::AlignTextToFramePadding();
                            ImGui::TextUnformatted(TR("lite_birthday_label"));
                            ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
                            ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
                            ImGui::InputInt("##LiteRestoreBirthday", &s_settingsState.lite_restore_birthday);
                            if (s_settingsState.lite_restore_birthday < 0) s_settingsState.lite_restore_birthday = 0;
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
                                TR("lite_birthday_hint"));

                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            ImGui::AlignTextToFramePadding();
                            ImGui::TextUnformatted(TR("lite_account_label"));
                            ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
                            ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
                            ImGui::InputInt("##LiteRestoreAccount", &s_settingsState.lite_restore_account);
                            if (s_settingsState.lite_restore_account < 0) s_settingsState.lite_restore_account = 0;

                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            ImGui::Checkbox(TrId("lite_overwrite", "LiteRestoreOverwrite").c_str(),
                                            &s_settingsState.lite_restore_overwrite);
                        }

                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        ImGui::AlignTextToFramePadding();
                        ImGui::TextUnformatted(TR("lite_passphrase_label"));
                        ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
                        ImGui::SetNextItemWidth(liteInputW);
                        ImGui::InputText("##LiteLifecyclePassphrase",
                                         s_settingsState.lite_lifecycle_passphrase,
                                         sizeof(s_settingsState.lite_lifecycle_passphrase),
                                         ImGuiInputTextFlags_Password);

                        // Poll a completed async create/open/restore (driven by the App-owned
                        // controller; finalized on the main thread by App::update's
                        // pumpLifecycleResult()). While in flight the button is disabled.
                        if (s_settingsState.lite_lifecycle_pending) {
                            if (auto* lite = app->liteWallet()) {
                                if (!lite->lifecycleRequestInProgress()) {
                                    s_settingsState.lite_lifecycle_pending = false;
                                    const auto& result = lite->lastLifecycleResult();
                                    s_settingsState.lite_lifecycle_summary = result.bridgeResponseRedacted;
                                    if (result.walletReady) {
                                        s_settingsState.lite_lifecycle_status = TR("lite_wallet_ready");
                                    } else {
                                        s_settingsState.lite_lifecycle_status = result.error.empty()
                                            ? result.status.message
                                            : result.error;
                                        Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
                                    }
                                }
                            } else {
                                s_settingsState.lite_lifecycle_pending = false;  // backend went away
                            }
                        }

                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        const bool liteLifecycleBusy = s_settingsState.lite_lifecycle_pending;
                        if (liteLifecycleBusy) ImGui::BeginDisabled();
                        if (TactileButton(TrId("lite_validate", "LiteLifecycleValidate").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                            evaluateLiteLifecycleRequestFromPageState(app);
                        }
                        if (liteLifecycleBusy) ImGui::EndDisabled();

                        if (!s_settingsState.lite_lifecycle_status.empty()) {
                            ImGui::SameLine(0, Layout::spacingSm());
                            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
                                               s_settingsState.lite_lifecycle_status.c_str());
                        }
                        if (!s_settingsState.lite_lifecycle_summary.empty()) {
                            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
                                               s_settingsState.lite_lifecycle_summary.c_str());
                        }
                    }

                    // Open the lite wallet data folder in the OS file manager (always available in
                    // lite). The lite backend stores its wallet in its OWN dir (silentdragonxlite),
                    // NOT the full-node getDragonXDataDir().
                    ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                    ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                    if (TactileButton(TR("settings_open_data_dir"), ImVec2(0, 0), S.resolveFont("button"))) {
                        util::Platform::openFolder(util::Platform::getLiteWalletDataDir());
                    }
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_data_dir"));

                    // ---- Backup & keys (open wallet only) ----------------------------------
                    if (app->liteWallet() && app->liteWallet()->walletOpen()) {
                        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        Type().text(TypeStyle::Body2, TR("lite_backup_keys"));

                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        if (TactileButton(TrId("lite_show_seed", "LiteExportSeed").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                            auto r = app->liteWallet()->exportSeed();
                            wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
                            if (r.ok) {
                                s_settingsState.lite_export_secret = r.seedPhrase;
                                s_settingsState.lite_export_label = TR("lite_seed_warning");
                                s_settingsState.lite_export_is_seed = true;
                                s_settingsState.lite_export_birthday = r.birthday;
                                s_settingsState.lite_backup_status.clear();
                            } else {
                                s_settingsState.lite_export_label.clear();
                                s_settingsState.lite_export_is_seed = false;
                                s_settingsState.lite_backup_status = r.error;
                            }
                            wallet::secureWipeLiteSecret(r.seedPhrase);  // wipe the result copy
                        }
                        ImGui::SameLine(0, Layout::spacingSm());
                        if (TactileButton(TrId("lite_show_private_keys", "LiteExportKeys").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                            auto r = app->liteWallet()->exportPrivateKeys();
                            wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
                            s_settingsState.lite_export_is_seed = false;  // keys, not a seed
                            if (r.ok) {
                                s_settingsState.lite_export_secret = r.privateKeysJson;
                                s_settingsState.lite_export_label = TR("lite_private_keys_warning");
                                s_settingsState.lite_backup_status.clear();
                            } else {
                                s_settingsState.lite_export_label.clear();
                                s_settingsState.lite_backup_status = r.error;
                            }
                            wallet::secureWipeLiteSecret(r.privateKeysJson);
                        }

                        // Revealed secret: shown read-only (no extra copies), with copy + wipe.
                        if (!s_settingsState.lite_export_secret.empty()) {
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
                                               s_settingsState.lite_export_label.c_str());
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            ImGui::PushTextWrapPos(0.0f);
                            ImGui::TextWrapped("%s", s_settingsState.lite_export_secret.c_str());
                            ImGui::PopTextWrapPos();
                            // The seed's birthday is needed to restore quickly — show + back it up too.
                            if (s_settingsState.lite_export_is_seed) {
                                char bday[64];
                                snprintf(bday, sizeof(bday), TR("lite_birthday_backup"),
                                         s_settingsState.lite_export_birthday);
                                ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                                Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), bday);
                            }
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            if (TactileButton(TrId("lite_copy", "LiteExportCopy").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                                app->copySecretToClipboard(s_settingsState.lite_export_secret);
                            }
                            ImGui::SameLine(0, Layout::spacingSm());
                            // Save the seed (+ birthday) to an owner-only (0600) file in the config dir.
                            if (s_settingsState.lite_export_is_seed &&
                                TactileButton(TrId("lite_save_to_file", "LiteExportSave").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                                std::string path = util::Platform::getConfigDir() + "/lite-seed-backup.txt";
                                std::string content = s_settingsState.lite_export_secret + "\nBirthday: " +
                                                      std::to_string(s_settingsState.lite_export_birthday) + "\n";
                                const bool ok = util::Platform::writeFileAtomically(
                                    path, content, /*restrictPermissions=*/true);
                                wallet::secureWipeLiteSecret(content);
                                s_settingsState.lite_backup_status = ok
                                    ? (std::string(TR("lite_saved_to")) + path)
                                    : (std::string(TR("lite_could_not_write")) + path);
                            }
                            if (s_settingsState.lite_export_is_seed) ImGui::SameLine(0, Layout::spacingSm());
                            if (TactileButton(TrId("lite_hide_wipe", "LiteExportHide").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                                wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
                                s_settingsState.lite_export_label.clear();
                                s_settingsState.lite_export_is_seed = false;
                            }
                        }

                        // Import a spending/viewing key (history appears after the next sync).
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        ImGui::AlignTextToFramePadding();
                        ImGui::TextUnformatted(TR("lite_import_key_label"));
                        ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
                        ImGui::SetNextItemWidth(liteInputW);
                        ImGui::InputText("##LiteImportKey", s_settingsState.lite_import_key,
                                         sizeof(s_settingsState.lite_import_key),
                                         ImGuiInputTextFlags_Password);
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        if (TactileButton(TrId("lite_import", "LiteImportKeyBtn").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                            std::string liteKey(s_settingsState.lite_import_key);
                            while (!liteKey.empty() && (liteKey.front()==' '||liteKey.front()=='\t'||liteKey.front()=='\n'||liteKey.front()=='\r')) liteKey.erase(liteKey.begin());
                            while (!liteKey.empty() && (liteKey.back()==' '||liteKey.back()=='\t'||liteKey.back()=='\n'||liteKey.back()=='\r')) liteKey.pop_back();
                            if (liteKey.empty()) {
                                s_settingsState.lite_backup_status = "Enter a private key to import.";
                            } else {
                                const auto r = app->liteWallet()->importKey(liteKey);
                                sodium_memzero(s_settingsState.lite_import_key, sizeof(s_settingsState.lite_import_key));
                                s_settingsState.lite_backup_status =
                                    r.ok ? TR("lite_key_imported") : r.error;
                            }
                        }

                        if (!s_settingsState.lite_backup_status.empty()) {
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
                                               s_settingsState.lite_backup_status.c_str());
                        }

                        // ---- Security: passphrase encryption (encrypt / unlock / lock / decrypt) ----
                        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        Type().text(TypeStyle::Body2, TR("lite_security"));
                        const auto& wstate = app->getWalletState();
                        const float encLabelX = leftX - sectionOrigin.x + liteLabelW;
                        if (!wstate.isEncrypted()) {
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            ImGui::AlignTextToFramePadding();
                            ImGui::TextUnformatted(TR("lite_passphrase_label"));
                            ImGui::SameLine(encLabelX);
                            ImGui::SetNextItemWidth(liteInputW);
                            ImGui::InputText("##LiteEncryptPass", s_settingsState.lite_enc_pass,
                                             sizeof(s_settingsState.lite_enc_pass), ImGuiInputTextFlags_Password);
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            if (TactileButton(TrId("lite_encrypt_wallet", "LiteEncrypt").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                                const auto r = app->liteWallet()->encryptWallet(s_settingsState.lite_enc_pass);
                                sodium_memzero(s_settingsState.lite_enc_pass, sizeof(s_settingsState.lite_enc_pass));
                                s_settingsState.lite_encryption_status = r.ok ? TR("lite_wallet_encrypted") : r.error;
                            }
                        } else {
                            if (wstate.isLocked()) {
                                ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                                ImGui::AlignTextToFramePadding();
                                ImGui::TextUnformatted(TR("lite_unlock"));
                                ImGui::SameLine(encLabelX);
                                ImGui::SetNextItemWidth(liteInputW);
                                ImGui::InputText("##LiteUnlockPass", s_settingsState.lite_enc_pass,
                                                 sizeof(s_settingsState.lite_enc_pass), ImGuiInputTextFlags_Password);
                                ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                                if (TactileButton(TrId("lite_unlock", "LiteUnlock").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                                    const bool ok = app->liteWallet()->unlockWallet(s_settingsState.lite_enc_pass);
                                    sodium_memzero(s_settingsState.lite_enc_pass, sizeof(s_settingsState.lite_enc_pass));
                                    s_settingsState.lite_encryption_status = ok ? TR("lite_wallet_unlocked") : TR("lite_unlock_failed");
                                }
                            } else {
                                ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                                if (TactileButton(TrId("lite_lock_now", "LiteLock").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                                    // Route through App so the chat session is torn down immediately on lock.
                                    s_settingsState.lite_encryption_status =
                                        app->lockLiteWallet() ? TR("lite_wallet_locked") : TR("lite_lock_failed");
                                }
                            }
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            ImGui::AlignTextToFramePadding();
                            ImGui::TextUnformatted(TR("lite_passphrase_label"));
                            ImGui::SameLine(encLabelX);
                            ImGui::SetNextItemWidth(liteInputW);
                            ImGui::InputText("##LiteDecryptPass", s_settingsState.lite_dec_pass,
                                             sizeof(s_settingsState.lite_dec_pass), ImGuiInputTextFlags_Password);
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            if (TactileButton(TrId("lite_remove_encryption", "LiteDecrypt").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
                                const auto r = app->liteWallet()->decryptWallet(s_settingsState.lite_dec_pass);
                                sodium_memzero(s_settingsState.lite_dec_pass, sizeof(s_settingsState.lite_dec_pass));
                                s_settingsState.lite_encryption_status = r.ok ? TR("lite_encryption_removed") : r.error;
                            }
                        }
                        if (!s_settingsState.lite_encryption_status.empty()) {
                            ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
                                               s_settingsState.lite_encryption_status.c_str());
                        }

                        // ---- Maintenance: re-download blocks from the lite server ----
                        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        Type().text(TypeStyle::Body2, TR("lite_maintenance"));

                        const bool scanning = app->liteWallet()->scanInProgress();
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        ImGui::BeginDisabled(scanning);
                        if (TactileButton(TR("lite_redownload_blocks"), ImVec2(0, 0), S.resolveFont("button"))) {
                            s_settingsState.confirm_lite_redownload = true;
                        }
                        ImGui::EndDisabled();
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
                            material::Tooltip("%s", TR("tt_lite_redownload"));
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
                                           scanning ? TR("lite_redownload_running") : TR("lite_redownload_desc"));
                    } else if (!s_settingsState.lite_export_secret.empty()) {
                        // Wallet closed while a backup/secret was revealed — don't leave it in memory.
                        wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
                        s_settingsState.lite_export_label.clear();
                        sodium_memzero(s_settingsState.lite_enc_pass, sizeof(s_settingsState.lite_enc_pass));
                        sodium_memzero(s_settingsState.lite_dec_pass, sizeof(s_settingsState.lite_dec_pass));
                    }
                }  // lite WALLET block
                ImGui::PopFont();
                }  // lite WALLET push-font scope

                // ---- SECURITY (lite): auto-lock + PIN only ----
                // The wallet's passphrase encryption controls live in the WALLET block
                // above (lite backend), so this section shows only the shared auto-lock
                // timeout + PIN controls.
                ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                renderSecuritySection(sectionOrigin.x, contentW, /*includeRpcEncrypt=*/false);

                // Advance to the true bottom of the single column.
                ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, ImGui::GetCursorScreenPos().y));

            } else {
                // ========================= FULL NODE =========================
                // One full-width column; each sub-section's controls flow left-to-right.
                ImGui::PushFont(body2Info);

                const float spMd = Layout::spacingMd();

                // -------------------- NODE / DATA --------------------
                Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("node"));
                ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                {
                    std::string dirPath   = util::Platform::getDragonXDataDir();
                    std::string walletPath = dirPath + "wallet.dat";
                    uint64_t   wallet_size = util::Platform::getFileSize(walletPath);
                    std::string size_str  = (wallet_size > 0)
                        ? util::Platform::formatFileSize(wallet_size) : TR("settings_not_found");

                    // Right-aligned "Open data folder" button trailing the row; the path
                    // gets whatever width is left after the data-dir label, wallet-size
                    // label+value, and the trailing button.
                    ImFont* nodeBtnFont = S.resolveFont("button");
                    if (!nodeBtnFont) nodeBtnFont = Type().button();
                    // Measure with the BUTTON font (not the current font) so the width matches
                    // the real TactileButton size — the old estimate mis-measured and the button
                    // overflowed the card's right edge, worse at high DPI.
                    float openBtnW = nodeBtnFont->CalcTextSizeA(nodeBtnFont->LegacySize, FLT_MAX, 0,
                                         TR("settings_open_data_dir")).x + ImGui::GetStyle().FramePadding.x * 2.0f;
                    // "Open app folder" (the ObsidianDragon config folder), left of the data button.
                    float appBtnW = nodeBtnFont->CalcTextSizeA(nodeBtnFont->LegacySize, FLT_MAX, 0,
                                        TR("settings_open_app_dir")).x + ImGui::GetStyle().FramePadding.x * 2.0f;
                    float openGroupW = appBtnW + spMd + openBtnW;
                    float dataDirLabelW  = ImGui::CalcTextSize(TR("settings_data_dir")).x;
                    float sizeLabelW     = ImGui::CalcTextSize(TR("settings_wallet_size_label")).x;
                    float sizeValueW     = ImGui::CalcTextSize(size_str.c_str()).x;
                    // Space reserved to the right of the path for the size block + both buttons.
                    float trailW = sizeLabelW + Layout::spacingXs() + sizeValueW + spMd + openGroupW;
                    float availForPath = contentW - dataDirLabelW - Layout::spacingXs() - spMd - trailW;
                    if (availForPath < 60.0f) availForPath = contentW - dataDirLabelW - Layout::spacingXs();

                    ImVec4 linkCol = ImGui::ColorConvertU32ToFloat4(Primary());
                    ImVec4 linkHoverCol = linkCol;
                    linkHoverCol.w = std::min(1.0f, linkCol.w + 0.2f);

                    float rowTopY = ImGui::GetCursorScreenPos().y;  // top of the data-dir row
                    ImGui::AlignTextToFramePadding();
                    ImGui::TextUnformatted(TR("settings_data_dir"));
                    ImGui::SameLine(0, Layout::spacingXs());
                    ImVec2 pathSize = ImGui::CalcTextSize(dirPath.c_str());
                    bool hovered = false;
                    ImGui::AlignTextToFramePadding();
                    if (pathSize.x > availForPath && availForPath > 0) {
                        float scale = availForPath / pathSize.x;
                        float condensedSize = body2Info->LegacySize * std::max(scale, 0.65f);
                        ImGui::SetWindowFontScale(condensedSize / body2Info->LegacySize);
                        ImGui::TextColored(linkCol, "%s", dirPath.c_str());
                        hovered = ImGui::IsItemHovered();
                        if (hovered) {
                            ImVec2 tMin = ImGui::GetItemRectMin();
                            ImVec2 tMax = ImGui::GetItemRectMax();
                            dl->AddLine(ImVec2(tMin.x, tMax.y), ImVec2(tMax.x, tMax.y), ImGui::GetColorU32(linkHoverCol));
                        }
                        ImGui::SetWindowFontScale(1.0f);
                    } else {
                        ImGui::TextColored(linkCol, "%s", dirPath.c_str());
                        hovered = ImGui::IsItemHovered();
                        if (hovered) {
                            ImVec2 tMin = ImGui::GetItemRectMin();
                            ImVec2 tMax = ImGui::GetItemRectMax();
                            dl->AddLine(ImVec2(tMin.x, tMax.y), ImVec2(tMax.x, tMax.y), ImGui::GetColorU32(linkHoverCol));
                        }
                    }
                    if (hovered) {
                        material::Tooltip("%s", TR("tt_open_dir"));
                        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                    }
                    if (ImGui::IsItemClicked())
                        util::Platform::openFolder(dirPath);

                    // Wallet Size label + value, trailing the path on the same row.
                    ImGui::SameLine(0, spMd);
                    ImGui::AlignTextToFramePadding();
                    ImGui::TextUnformatted(TR("settings_wallet_size_label"));
                    ImGui::SameLine(0, Layout::spacingXs());
                    ImGui::AlignTextToFramePadding();
                    if (wallet_size > 0)
                        ImGui::TextUnformatted(size_str.c_str());
                    else
                        ImGui::TextDisabled("%s", TR("settings_not_found"));

                    // Folder buttons: [Open app folder] [Open data folder], right-aligned as a
                    // group on the data-dir row (rowTopY), each an EXPLICIT width so the group's
                    // right edge lands exactly on the card edge (no overflow at any DPI).
                    float rowY = rowTopY;
                    float rightEdge = sectionOrigin.x + contentW;
                    float groupX = rightEdge - openGroupW;
                    float afterSizeX = ImGui::GetItemRectMax().x + spMd;
                    ImGui::SameLine(0, spMd);
                    if (groupX > afterSizeX)
                        ImGui::SetCursorScreenPos(ImVec2(groupX, rowY));
                    // App (ObsidianDragon config) folder — opens ~/.config/ObsidianDragon etc.
                    if (TactileButton(TR("settings_open_app_dir"), ImVec2(appBtnW, 0), nodeBtnFont)) {
                        util::Platform::openFolder(util::Platform::getObsidianDragonDir());
                    }
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_app_dir"));
                    ImGui::SameLine(0, spMd);
                    if (TactileButton(TR("settings_open_data_dir"), ImVec2(openBtnW, 0), nodeBtnFont)) {
                        util::Platform::openFolder(dirPath);
                    }
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_data_dir"));
                }

                // -------------------- RPC (collapsible, like Tools & Actions) --------------------
                ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                {
                    ImFont* rpcHdrFont = body2;  // match the "Tools & Actions..." toggle style
                    const char* rpcArrow = s_settingsState.rpc_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
                    ImGui::PushFont(body2);
                    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
                    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
                    ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
                    ImVec2 hdrPos = ImGui::GetCursorScreenPos();
                    if (ImGui::Button("##RpcToggle", ImVec2(contentW, ImGui::GetFrameHeight())))
                        s_settingsState.rpc_expanded = !s_settingsState.rpc_expanded;
                    if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                    float textY = hdrPos.y + (ImGui::GetFrameHeight() - rpcHdrFont->LegacySize) * 0.5f;
                    dl->AddText(rpcHdrFont, rpcHdrFont->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("rpc_connection"));
                    ImFont* rpcIconFont = Type().iconSmall();
                    if (!rpcIconFont) rpcIconFont = body2;
                    float rpcArrowW = rpcIconFont->CalcTextSizeA(rpcIconFont->LegacySize, FLT_MAX, 0, rpcArrow).x;
                    dl->AddText(rpcIconFont, rpcIconFont->LegacySize, ImVec2(hdrPos.x + contentW - rpcArrowW, textY), OnSurfaceMedium(), rpcArrow);
                    ImGui::PopStyleColor(3);
                    ImGui::PopFont();
                }
                if (s_settingsState.rpc_expanded) {
                    ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                    // Four fields (Host, Port, Username, Password) across one row when the
                    // card is wide enough; otherwise 2x2. Each field is "label [input]".
                    const char* hostLbl = TR("rpc_host");
                    const char* portLbl = TR("rpc_port");
                    const char* userLbl = TR("rpc_user");
                    const char* passLbl = TR("rpc_pass");
                    float labelsW = ImGui::CalcTextSize(hostLbl).x + ImGui::CalcTextSize(portLbl).x +
                                    ImGui::CalcTextSize(userLbl).x + ImGui::CalcTextSize(passLbl).x;

                    const bool fourAcross = contentW >= 700.0f;
                    auto field = [&](const char* label, const char* id, char* buf, size_t bufSz,
                                     float inputW, bool password) {
                        ImGui::AlignTextToFramePadding();
                        ImGui::TextUnformatted(label);
                        ImGui::SameLine(0, Layout::spacingXs());
                        ImGui::SetNextItemWidth(inputW);
                        // Read-only: the RPC credentials are auto-detected from the daemon's DRAGONX.conf,
                        // so these fields DISPLAY the live connection (editing them here did nothing).
                        ImGui::InputText(id, buf, bufSz,
                                         ImGuiInputTextFlags_ReadOnly |
                                         (password ? ImGuiInputTextFlags_Password : 0));
                    };

                    if (fourAcross) {
                        // fieldW = (contentW - labels - per-field label gaps - 3 inter-field gaps) / 4
                        float inputTotal = contentW - labelsW - Layout::spacingXs() * 4 - spMd * 3;
                        float inputW = std::max(60.0f, std::floor(inputTotal / 4.0f));
                        field(hostLbl, "##RPCHost", s_settingsState.rpc_host, sizeof(s_settingsState.rpc_host), inputW, false);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_host"));
                        ImGui::SameLine(0, spMd);
                        field(portLbl, "##RPCPort", s_settingsState.rpc_port, sizeof(s_settingsState.rpc_port), inputW, false);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_port"));
                        ImGui::SameLine(0, spMd);
                        field(userLbl, "##RPCUser", s_settingsState.rpc_user, sizeof(s_settingsState.rpc_user), inputW, false);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_user"));
                        ImGui::SameLine(0, spMd);
                        field(passLbl, "##RPCPassword", s_settingsState.rpc_password, sizeof(s_settingsState.rpc_password), inputW, true);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_pass"));
                    } else {
                        // 2x2: two fields per row.
                        float halfLabelsW = ImGui::CalcTextSize(hostLbl).x + ImGui::CalcTextSize(userLbl).x;
                        float inputW = std::max(60.0f, std::floor(
                            (contentW - halfLabelsW - Layout::spacingXs() * 2 - spMd) / 2.0f));
                        field(hostLbl, "##RPCHost", s_settingsState.rpc_host, sizeof(s_settingsState.rpc_host), inputW, false);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_host"));
                        ImGui::SameLine(0, spMd);
                        field(userLbl, "##RPCUser", s_settingsState.rpc_user, sizeof(s_settingsState.rpc_user), inputW, false);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_user"));

                        field(portLbl, "##RPCPort", s_settingsState.rpc_port, sizeof(s_settingsState.rpc_port), inputW, false);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_port"));
                        ImGui::SameLine(0, spMd);
                        field(passLbl, "##RPCPassword", s_settingsState.rpc_password, sizeof(s_settingsState.rpc_password), inputW, true);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_pass"));
                    }

                    Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("settings_auto_detected"));
                    if (s_settingsState.rpc_plaintext_remote) {
                        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning()));
                        ImGui::PushTextWrapPos(sectionOrigin.x + contentW);
                        ImGui::TextWrapped("Remote RPC is using plaintext HTTP. Add rpctls=1 to DRAGONX.conf if your daemon supports TLS.");
                        ImGui::PopTextWrapPos();
                        ImGui::PopStyleColor();
                    }
                }

                // -------------------- SECURITY --------------------
                ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("security"));
                ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                renderSecuritySection(sectionOrigin.x, contentW, /*includeRpcEncrypt=*/true);

                // -------------------- DAEMON BINARY --------------------
                if (app->supportsFullNodeLifecycleActions()) {
                    ImFont* dbBtnFont = S.resolveFont("button");
                    if (!s_settingsState.daemon_info_loaded) {
                        s_settingsState.installed_daemon = dragonx::resources::getInstalledDaemonInfo();
                        s_settingsState.bundled_daemon   = dragonx::resources::getBundledDaemonInfo();
                        s_settingsState.daemon_info_loaded = true;
                    }
                    const auto& inst = s_settingsState.installed_daemon;
                    const auto& bun  = s_settingsState.bundled_daemon;

                    auto fmtDate = [](std::int64_t epoch) -> std::string {
                        if (epoch <= 0) return "—";
                        std::time_t t = static_cast<std::time_t>(epoch);
                        std::tm tmv{};
#ifdef _WIN32
                        localtime_s(&tmv, &t);
#else
                        localtime_r(&t, &tmv);
#endif
                        char buf[32];
                        std::strftime(buf, sizeof(buf), "%Y-%m-%d", &tmv);
                        return std::string(buf);
                    };

                    ImGui::Dummy(ImVec2(0, Layout::spacingLg()));
                    Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary"));
                    ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

                    // Status line: Installed <v> (<size> · <date>) · Bundled <v> (<size>) · <status>.
                    // Built as color-tagged atoms and laid out with manual wrapping — as one long inline
                    // row of SameLine() Text calls it overran the content width and clipped its colored
                    // tail at narrow windows / higher font scale (the status was effectively hidden).
                    struct DbAtom { std::string text; ImU32 col; bool sep; };
                    std::vector<DbAtom> dbAtoms;
                    const ImU32 dbNorm = ImGui::GetColorU32(ImGuiCol_Text);
                    const ImU32 dbDimU = OnSurfaceMedium();

                    dbAtoms.push_back({ TR("daemon_installed"), dbNorm, false });
                    if (inst.exists) {
                        dbAtoms.push_back({ inst.version.empty() ? std::string(TR("unknown")) : inst.version, dbNorm, false });
                        char dbParen[96];
                        std::snprintf(dbParen, sizeof(dbParen), "(%s · %s)",
                                      util::Platform::formatFileSize(inst.size).c_str(),
                                      fmtDate(inst.modifiedEpoch).c_str());
                        dbAtoms.push_back({ dbParen, dbDimU, false });
                    } else {
                        dbAtoms.push_back({ TR("daemon_not_installed"), dbDimU, false });
                    }
                    dbAtoms.push_back({ "·", dbDimU, true });
                    dbAtoms.push_back({ TR("daemon_bundled"), dbNorm, false });
                    if (bun.available) {
                        dbAtoms.push_back({ bun.version.empty() ? std::string(TR("unknown")) : bun.version, dbNorm, false });
                        dbAtoms.push_back({ "(" + util::Platform::formatFileSize(bun.size) + ")", dbDimU, false });
                    } else {
                        dbAtoms.push_back({ TR("daemon_none_bundled"), dbDimU, false });
                    }
                    if (bun.available) {
                        const bool sameSize = inst.exists && inst.size == bun.size;
                        dbAtoms.push_back({ "·", dbDimU, true });
                        if (!inst.exists)
                            dbAtoms.push_back({ TR("daemon_status_missing"), ImGui::ColorConvertFloat4ToU32(ImVec4(1.0f, 0.8f, 0.2f, 1.0f)), false });
                        else if (sameSize)
                            dbAtoms.push_back({ TR("daemon_status_match"),   ImGui::ColorConvertFloat4ToU32(ImVec4(0.3f, 0.8f, 0.3f, 1.0f)), false });
                        else
                            dbAtoms.push_back({ TR("daemon_status_differ"),  ImGui::ColorConvertFloat4ToU32(ImVec4(1.0f, 0.8f, 0.2f, 1.0f)), false });
                    }

                    // Lay the atoms out left-to-right, wrapping to a new line when the next one would
                    // exceed the content width. A "·" separator is dropped when it (plus the atom after
                    // it) doesn't fit, so a wrapped line never starts or ends with a dangling separator.
                    const float dbAvail = ImGui::GetContentRegionAvail().x;
                    const float dbGap = Layout::spacingXs();
                    float dbX = 0.0f;
                    ImGui::AlignTextToFramePadding();
                    for (std::size_t i = 0; i < dbAtoms.size(); ++i) {
                        const float w = ImGui::CalcTextSize(dbAtoms[i].text.c_str()).x;
                        if (dbAtoms[i].sep) {
                            const float nextW = (i + 1 < dbAtoms.size())
                                ? ImGui::CalcTextSize(dbAtoms[i + 1].text.c_str()).x : 0.0f;
                            if (dbX > 0.0f && dbX + dbGap + w + dbGap + nextW > dbAvail) continue;
                        }
                        const bool wrap = (dbX > 0.0f) && (dbX + dbGap + w > dbAvail);
                        if (dbX > 0.0f && !wrap) { ImGui::SameLine(0, dbGap); dbX += dbGap; }
                        else if (wrap)          { dbX = 0.0f; }
                        ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(dbAtoms[i].col), "%s", dbAtoms[i].text.c_str());
                        dbX += w;
                    }

                    // In-app node update: download + verify the latest dragonxd from the project Gitea.
                    // Refresh the cached daemon info once an install has completed.
                    if (ui::DaemonUpdateDialog::consumeInstalled())
                        s_settingsState.daemon_info_loaded = false;

                    // Action row: Check for updates | Refresh | Install bundled on the left,
                    // with the four maintenance actions right-aligned on the same row.
                    ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                    float daemonBtnRowY = ImGui::GetCursorScreenPos().y;
                    if (TactileButton(TR("daemon_update_check"), ImVec2(0, 0), dbBtnFont)) {
                        ui::DaemonUpdateDialog::show(app, inst.exists ? inst.version : std::string());
                    }
                    if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_update_check"));
                    ImGui::SameLine(0, spMd);
                    // Unique ImGui ID (label still displays "Refresh") — the theme sections
                    // also have "Refresh" buttons, so a bare label collides on this page.
                    if (TactileButton((std::string(TR("refresh")) + "##daemonRefresh").c_str(), ImVec2(0, 0), dbBtnFont)) {
                        s_settingsState.daemon_info_loaded = false;
                    }
                    ImGui::SameLine(0, spMd);
                    ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
                    if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), dbBtnFont)) {
                        s_settingsState.confirm_reinstall_daemon = true;
                    }
                    if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_install_bundled"));
                    ImGui::EndDisabled();

                    // The four maintenance actions (Test connection / Rescan / Delete blockchain
                    // / Repair wallet), right-aligned on the SAME row as Install bundled — no
                    // separate "Advanced" toggle. Every BeginDisabled/EndDisabled predicate is
                    // preserved exactly. Wraps to a right-aligned next row when too narrow.
                    auto renderDaemonMaintenanceButtons = [&]() {
                        ImGui::BeginDisabled(!app->isConnected());
                        if (TactileButton(TR("test_connection"), ImVec2(0, 0), dbBtnFont)) {
                            if (app->rpc() && app->rpc()->isConnected() && app->worker()) {
                                app->worker()->post([rpc = app->rpc()]() -> rpc::RPCWorker::MainCb {
                                    try {
                                        rpc::RPCClient::TraceScope trace("Settings / Test connection");
                                        rpc->call("getinfo");
                                        return []() { Notifications::instance().success("RPC connection OK"); };
                                    } catch (const std::exception& e) {
                                        std::string err = e.what();
                                        return [err]() { Notifications::instance().error("RPC error: " + err); };
                                    }
                                });
                            } else {
                                Notifications::instance().warning("Not connected to daemon");
                            }
                        }
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
                        ImGui::EndDisabled();
                        ImGui::SameLine(0, spMd);
                        ImGui::BeginDisabled(!app->isConnected());
                        if (TactileButton(TR("rescan"), ImVec2(0, 0), dbBtnFont)) {
                            s_settingsState.confirm_rescan = true;
                            // Re-probe the available block range each time the dialog is opened.
                            s_settingsState.rescan_height_detecting = false;
                            s_settingsState.rescan_height_detected = false;
                        }
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_rescan"));
                        ImGui::EndDisabled();
                        ImGui::SameLine(0, spMd);
                        ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
                        if (TactileButton(TR("delete_blockchain"), ImVec2(0, 0), dbBtnFont)) {
                            s_settingsState.confirm_delete_blockchain = true;
                        }
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_delete_blockchain"));
                        ImGui::SameLine(0, spMd);
                        if (TactileButton(TR("repair_wallet"), ImVec2(0, 0), dbBtnFont)) {
                            s_settingsState.confirm_repair_wallet = true;
                        }
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_repair_wallet"));
                        ImGui::EndDisabled();
                    };
                    {
                        // Group width (button font) to right-align the four maintenance actions.
                        const char* mLabels[] = { TR("test_connection"), TR("rescan"),
                                                  TR("delete_blockchain"), TR("repair_wallet") };
                        float groupW = spMd * 3.0f;
                        for (const char* l : mLabels)
                            groupW += dbBtnFont->CalcTextSizeA(dbBtnFont->LegacySize, FLT_MAX, 0, l).x
                                      + ImGui::GetStyle().FramePadding.x * 2.0f;
                        float afterTestX = ImGui::GetItemRectMax().x + spMd;   // right edge of Install bundled
                        float rightEdge  = sectionOrigin.x + contentW;
                        float groupX     = rightEdge - groupW;
                        if (groupX >= afterTestX) {
                            // Fits on the Install-bundled row, right-aligned.
                            ImGui::SameLine(0, 0);
                            ImGui::SetCursorScreenPos(ImVec2(groupX, daemonBtnRowY));
                        } else {
                            // Too narrow: wrap to the next row, right-aligned (clamped to left edge).
                            ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                            float wrapX = std::max(sectionOrigin.x, rightEdge - groupW);
                            ImGui::SetCursorScreenPos(ImVec2(wrapX, ImGui::GetCursorScreenPos().y));
                        }
                        renderDaemonMaintenanceButtons();
                    }
                }

                ImGui::PopFont();

                // Advance to the true bottom of the single column.
                ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, ImGui::GetCursorScreenPos().y));
            }
        }
    }

    ImGui::Dummy(ImVec2(0, gap));

    // ====================================================================
    // EXPLORER & OPTIONS — full-width card
    // ====================================================================
    {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("explorer_section"));
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

        material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);

        float contentW = availWidth - pad * 2;
        ImGui::PushFont(body2);

        // Row 1: Transaction URL | Address URL  (side-by-side)
        float halfW = (contentW - Layout::spacingLg()) * 0.5f;
        float lblTxW = ImGui::CalcTextSize("Transaction URL").x + Layout::spacingXs();
        float lblAddrW = ImGui::CalcTextSize("Address URL").x + Layout::spacingXs();
        float inputTxW = std::max(80.0f, halfW - lblTxW);
        float inputAddrW = std::max(80.0f, halfW - lblAddrW);

        ImGui::AlignTextToFramePadding();
        ImGui::TextUnformatted(TR("transaction_url"));
        ImGui::SameLine(0, Layout::spacingXs());
        ImGui::SetNextItemWidth(inputTxW);
        ImGui::InputText("##TxExplorer", s_settingsState.tx_explorer, sizeof(s_settingsState.tx_explorer));
        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_tx_url"));
        ImGui::SameLine(pad + halfW + Layout::spacingLg());
        ImGui::AlignTextToFramePadding();
        ImGui::TextUnformatted(TR("address_url"));
        ImGui::SameLine(0, Layout::spacingXs());
        ImGui::SetNextItemWidth(inputAddrW);
        ImGui::InputText("##AddrExplorer", s_settingsState.addr_explorer, sizeof(s_settingsState.addr_explorer));
        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_addr_url"));

        ImGui::PopFont();
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

        // Row 2: Checkboxes + Block Explorer button  (on one line)
        ImGui::Checkbox(TrId("custom_fees", "custom_fees").c_str(), &s_settingsState.allow_custom_fees);
        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_custom_fees"));
        ImGui::SameLine(0, Layout::spacingLg());
        ImGui::Checkbox(TrId("fetch_prices", "fetch_prices").c_str(), &s_settingsState.fetch_prices);
        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_fetch_prices"));
        ImGui::SameLine(0, Layout::spacingLg());
        if (TactileButton(TR("block_explorer"), ImVec2(0, 0), S.resolveFont("button"))) {
            util::Platform::openUrl("https://explorer.dragonx.is");
        }
        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_block_explorer"));
    }

    ImGui::Dummy(ImVec2(0, gap));

    // ====================================================================
    // ABOUT — card
    // ====================================================================
    {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("about"));
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

        ImVec2 cardMin = ImGui::GetCursorScreenPos();
        dl->ChannelsSplit(2);
        dl->ChannelsSetCurrent(1);
        ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
        ImGui::Indent(pad);

        // Logo on the left side of the about card. Deferred: reserve horizontal space
        // now, but draw the image after the card's final height is known so it scales to
        // the full card height (no empty space below it).
        ImTextureID logoTex = app->getLogoTexture();
        float logoAreaW = 0;
        ImVec2 logoPos = ImGui::GetCursorScreenPos();
        float logoAspect = (app->getLogoHeight() > 0)
            ? (float)app->getLogoWidth() / (float)app->getLogoHeight() : 1.0f;
        float logoReserveH = schema::UI().drawElement("components.settings-page", "about-logo-size").sizeOr(150.0f);
        if (logoTex != 0) {
            logoAreaW = logoReserveH * logoAspect + Layout::spacingLg();
            ImGui::Indent(logoAreaW);
        }

        float contentW = availWidth - pad * 2 - logoAreaW;

        // App name + version on same line
        ImGui::PushFont(sub1);
        ImGui::TextUnformatted(DRAGONX_APP_NAME);
        ImGui::PopFont();
        ImGui::SameLine(0, Layout::spacingLg());
        ImGui::PushFont(body2);
        snprintf(buf, sizeof(buf), "v%s", DRAGONX_VERSION);
        ImGui::TextUnformatted(buf);
        ImGui::SameLine(0, Layout::spacingLg());
        snprintf(buf, sizeof(buf), "ImGui %s", IMGUI_VERSION);
        ImGui::TextColored(ImVec4(1,1,1,0.4f), "%s", buf);
        ImGui::PopFont();

        // Daemon version
        {
            const auto& st = app->state();
            if (st.daemon_version > 0) {
                int dmaj = st.daemon_version / 1000000;
                int dmin = (st.daemon_version / 10000) % 100;
                int dpat = (st.daemon_version / 100) % 100;
                ImGui::PushFont(body2);
                snprintf(buf, sizeof(buf), "%s: %d.%d.%d", TR("daemon_version"), dmaj, dmin, dpat);
                ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", buf);
                ImGui::PopFont();
            }
        }

        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

        ImGui::PushFont(body2);
        ImGui::PushTextWrapPos(cardMin.x + availWidth - pad - logoAreaW);
        ImGui::TextUnformatted(TR("settings_about_text"));
        ImGui::PopTextWrapPos();
        ImGui::PopFont();

        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

        ImGui::PushFont(capFont);
        ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_copyright"));
        ImGui::PopFont();

        ImGui::Dummy(ImVec2(0, Layout::spacingMd()));

        // Top of the (full-width) buttons row — the deferred logo is clamped to end above
        // this Y so the tall left-column logo never overlaps the buttons.
        float aboutButtonsTopY = ImGui::GetCursorScreenPos().y;

        // Buttons — consistent equal-width row (full card width)
        if (logoAreaW > 0) {
            ImGui::Unindent(logoAreaW);
        }
        {
            float fullContentW = availWidth - pad * 2;
            float aboutBtnW = (fullContentW - Layout::spacingMd() * 3) / 4.0f;

            if (TactileButton(TrId("website", "about_website").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
                util::Platform::openUrl("https://dragonx.is");
            }
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_website"));
            ImGui::SameLine(0, Layout::spacingMd());
            if (TactileButton(TrId("report_bug", "about_bug").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
                util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon/issues");
            }
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_report_bug"));
            ImGui::SameLine(0, Layout::spacingMd());
            if (TactileButton(TrId("save_settings", "about_save").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
                saveSettingsPageState(app->settings());
                Notifications::instance().success("Settings saved");
            }
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_save_settings"));
            ImGui::SameLine(0, Layout::spacingMd());
            if (TactileButton(TrId("reset_to_defaults", "about_reset").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
                if (app->settings()) {
                    loadSettingsPageState(app->settings());
                    Notifications::instance().info("Settings reloaded from disk");
                }
            }
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_reset_settings"));
        }

        ImGui::Dummy(ImVec2(0, bottomPad));
        ImGui::Unindent(pad);

        ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);

        // Draw the logo now that the card height is known — aspect-preserved, capped to the
        // reserved width, and clamped to end just above the buttons row so it never overlaps
        // the text or the buttons. Still on the content channel (1), above the glass.
        if (logoTex != 0) {
            float reserveW = logoReserveH * logoAspect;
            // Height available above the buttons row (the fix for the logo/buttons overlap).
            float logoBottomLimit = aboutButtonsTopY - Layout::spacingSm();
            float logoH = std::max(16.0f, logoBottomLimit - logoPos.y);
            float logoW = logoH * logoAspect;
            if (logoW > reserveW) { logoW = reserveW; logoH = (logoAspect > 0.0f) ? logoW / logoAspect : logoH; }
            dl->AddImage(logoTex, logoPos, ImVec2(logoPos.x + logoW, logoPos.y + logoH));
        }

        dl->ChannelsSetCurrent(0);
        DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
        dl->ChannelsMerge();

        ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
        ImGui::Dummy(ImVec2(availWidth, 0));
    }

    ImGui::Dummy(ImVec2(0, gap));

    // ====================================================================
    // DEBUG OPTIONS — collapsible card (full-node only: holds the screenshot sweep + the dragonxd
    // daemon debug= categories written to DRAGONX.conf; lite has no daemon)
    // ====================================================================
    if (app->supportsFullNodeLifecycleActions()) {
        // Clickable header row
        ImVec2 headerPos = ImGui::GetCursorScreenPos();
        const char* arrow = s_settingsState.debug_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
        ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
        if (ImGui::Button("##DebugToggle", ImVec2(availWidth, ImGui::GetFrameHeight()))) {
            if (s_settingsState.debug_expanded) {
                s_settingsState.debug_expanded = false;   // collapsing needs no gate
            } else if (s_settingsState.debug_gate_passed) {
                s_settingsState.debug_expanded = true;    // already unlocked this session
            } else {
                // First expand this session is gated: confirmation + (if secured) re-auth.
                s_settingsState.debug_gate_open = true;
                s_settingsState.debug_gate_buf[0] = '\0';
                s_settingsState.debug_gate_err.clear();
                s_settingsState.debug_gate_verifying = false;
            }
        }
        if (ImGui::IsItemHovered()) material::Tooltip("%s", s_settingsState.debug_expanded ? TR("tt_debug_collapse") : TR("tt_debug_expand"));
        ImGui::PopStyleColor(3);

        // Draw overline label + arrow on top of the invisible button
        {
            ImFont* ovFont = Type().overline();
            float textY = headerPos.y + (ImGui::GetFrameHeight() - ovFont->LegacySize) * 0.5f;
            dl->AddText(ovFont, ovFont->LegacySize, ImVec2(headerPos.x, textY), OnSurfaceMedium(), TR("debug_logging"));
            // Use the icon font for the expand/collapse arrow
            ImFont* iconFont = Type().iconSmall();
            if (!iconFont) iconFont = ovFont;
            float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
            dl->AddText(iconFont, iconFont->LegacySize, ImVec2(headerPos.x + availWidth - arrowW - pad, textY), OnSurfaceMedium(), arrow);
        }

        if (s_settingsState.debug_expanded) {
            ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

            material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);

            // Screenshot sweep — capture every theme x every tab (above the daemon debug categories).
            ImGui::TextWrapped("%s", TR("screenshot_sweep_desc"));
            ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
            if (TactileButton(TR("screenshot_sweep"), ImVec2(0, 0), S.resolveFont("button")))
                app->startScreenshotSweep();
            ImGui::SameLine();
            // Full UI sweep: also captures every modal / dialog / flow / state (demo data, offline).
            if (TactileButton(TR("screenshot_sweep_full"), ImVec2(0, 0), S.resolveFont("button")))
                app->startFullUiSweep();
            ImGui::SameLine();
            if (TactileButton(TR("screenshot_open_dir"), ImVec2(0, 0), S.resolveFont("button")))
                util::Platform::openFolder(app->screenshotDir());
            if (chat::hushChatFeatureEnabledAtBuild()) {
                // Populate the Chat tab with demo conversations so the sweep captures its real UI.
                ImGui::SameLine();
                if (TactileButton("Seed demo chat", ImVec2(0, 0), S.resolveFont("button")))
                    app->seedChatDemoData();
            }
            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
            ImGui::Separator();
            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

            ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_debug_select"));
            ImGui::TextColored(ImVec4(1,1,1,0.35f), "%s", TR("settings_debug_restart_note"));
            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

            // The 22 dragonxd debug categories
            static const char* debugCats[] = {
                "addrman", "alert", "bench", "coindb", "db", "estimatefee",
                "http", "libevent", "lock", "mempool", "net",
                "paymentdisclosure", "pow", "proxy", "prune", "rand",
                "reindex", "rpc", "selectcoins", "tor", "zmq", "zrpc"
            };
            static const char* debugTips[] = {
                "Peer address tracking and management",
                "Alert system messages",
                "Benchmark timings for operations",
                "Coin database read/write operations",
                "Berkeley DB operations",
                "Fee estimation algorithm",
                "HTTP RPC server activity",
                "Libevent networking library",
                "Lock contention debugging",
                "Transaction memory pool activity",
                "Network connections and messages",
                "Payment disclosure protocol",
                "Proof-of-work mining activity",
                "SOCKS5 proxy connections",
                "Block pruning operations",
                "Random number generation",
                "Blockchain reindexing progress",
                "RPC command processing",
                "Coin selection for transactions",
                "Tor integration and circuit info",
                "ZeroMQ notification system",
                "Shielded (z-addr) RPC operations"
            };
            constexpr int numCats = sizeof(debugCats) / sizeof(debugCats[0]);

            // Render as a 4-column grid of checkboxes
            int columns = 4;
            float dbgContentW = availWidth - pad * 2.0f;
            float colW = dbgContentW / columns;

            for (int i = 0; i < numCats; i++) {
                if (i > 0 && (i % columns) != 0) {
                    ImGui::SameLine(pad + colW * (i % columns));
                }
                bool enabled = s_settingsState.debug_categories.count(debugCats[i]) > 0;
                if (ImGui::Checkbox(debugCats[i], &enabled)) {
                    if (enabled) {
                        s_settingsState.debug_categories.insert(debugCats[i]);
                    } else {
                        s_settingsState.debug_categories.erase(debugCats[i]);
                    }
                    s_settingsState.debug_cats_dirty = true;
                    saveSettingsPageState(app->settings());
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", debugTips[i]);
            }

            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

            // "Restart daemon" button — only active when categories changed
            if (s_settingsState.debug_cats_dirty && app->supportsFullNodeLifecycleActions()) {
                ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 218, 0, 255));
                ImFont* iconFont = Type().iconSmall();
                if (iconFont) {
                    ImGui::PushFont(iconFont);
                    ImGui::AlignTextToFramePadding();
                    ImGui::TextUnformatted(ICON_MD_INFO);
                    ImGui::PopFont();
                    ImGui::SameLine(0, Layout::spacingXs());
                }
                ImGui::AlignTextToFramePadding();
                ImGui::TextUnformatted(TR("settings_debug_changed"));
                ImGui::PopStyleColor();
                ImGui::SameLine();
                if (TactileButton(TR("settings_restart_daemon"), ImVec2(0, 0), S.resolveFont("button"))) {
                    s_settingsState.confirm_restart_daemon = true;
                }
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_restart_daemon"));
            }
        }

        // ---- Debug-options gate: confirmation + warning (+ re-auth if a PIN/passphrase is set) ----
        if (s_settingsState.debug_gate_open) {
            auto& st = s_settingsState;
            if (st.debug_gate_err_timer > 0.0f) {
                st.debug_gate_err_timer -= ImGui::GetIO().DeltaTime;
                if (st.debug_gate_err_timer <= 0.0f) st.debug_gate_err.clear();
            }
            const bool needAuth = app->debugGateRequiresAuth();
            const bool hasPin   = app->hasPinVault();
            const float gdp = Layout::dpiScale();
            material::OverlayDialogSpec ov;
            ov.title = TR("debug_gate_title");
            ov.p_open = &st.debug_gate_open;
            ov.style = material::OverlayStyle::BlurFloat;
            ov.cardWidth = 480.0f;
            ov.idSuffix = "debuggate";
            if (material::BeginOverlayDialog(ov)) {
                if (ImGui::IsKeyPressed(ImGuiKey_Escape)) st.debug_gate_open = false;
                ImGui::TextWrapped("%s", TR("debug_gate_warning"));
                ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

                bool submit = false;
                if (needAuth) {
                    Type().textColored(TypeStyle::Caption, OnSurfaceMedium(),
                                       hasPin ? TR("debug_gate_pin_prompt") : TR("debug_gate_pass_prompt"));
                    ImGui::SetNextItemWidth(-FLT_MIN);
                    ImGuiInputTextFlags f = ImGuiInputTextFlags_Password | ImGuiInputTextFlags_EnterReturnsTrue;
                    if (hasPin) f |= ImGuiInputTextFlags_CharsDecimal;   // PIN is numeric
                    if (ImGui::InputText("##debugGateSecret", st.debug_gate_buf, sizeof(st.debug_gate_buf), f))
                        submit = true;
                    if (!st.debug_gate_err.empty()) {
                        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                        Type().textColored(TypeStyle::Caption, Error(), st.debug_gate_err.c_str());
                    }
                }

                ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                ImGui::BeginDisabled(st.debug_gate_verifying);
                if (TactileButton(TR("debug_gate_confirm"), ImVec2(170.0f * gdp, 0)) || submit) {
                    if (!needAuth) {
                        st.debug_expanded = true;
                        st.debug_gate_passed = true;
                        st.debug_gate_open = false;
                    } else if (strlen(st.debug_gate_buf) > 0) {
                        st.debug_gate_verifying = true;
                        std::string secret = st.debug_gate_buf;
                        memset(st.debug_gate_buf, 0, sizeof(st.debug_gate_buf));
                        app->verifyDebugCredential(secret, [](bool ok) {
                            auto& s = s_settingsState;
                            s.debug_gate_verifying = false;
                            if (ok) { s.debug_expanded = true; s.debug_gate_passed = true; s.debug_gate_open = false; }
                            else    { s.debug_gate_err = TR("debug_gate_incorrect"); s.debug_gate_err_timer = 4.0f; }
                        });
                        if (!secret.empty()) memset(&secret[0], 0, secret.size());
                    }
                }
                ImGui::EndDisabled();
                ImGui::SameLine();
                if (TactileButton(TR("cancel"), ImVec2(120.0f * gdp, 0))) {
                    memset(st.debug_gate_buf, 0, sizeof(st.debug_gate_buf));
                    st.debug_gate_open = false;
                }
                if (st.debug_gate_verifying) {
                    ImGui::SameLine();
                    ImGui::AlignTextToFramePadding();
                    Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("debug_gate_verifying"));
                }
                material::EndOverlayDialog();
            }
        }
    }

    // --- Shader-based scroll fade: unbind (restore ImGui's default shader) ---
    if (fadeH > 0.0f && !s_settingsState.low_spec_mode && s_settingsState.fade_shader.ready) {
        effects::ScrollFadeShader::addUnbind(dl);
    }

    // --- Vertex-based alpha fade for ForegroundDrawList theme effects ---
    // DrawGlassPanel draws rainbow borders, shimmer, specular glare, and
    // edge traces on the ForegroundDrawList which bypasses the shader.
    // Apply the same fade boundaries via vertex alpha manipulation.
    if (fadeH > 0.0f) {
        int fgVtxEnd = fgDL->VtxBuffer.Size;
        float safeTop = settingsFadeTopY + settingsFadeZoneTop;
        float safeBot = settingsFadeBottomY - settingsFadeZoneBot;
        for (int vi = fgVtxStart; vi < fgVtxEnd; vi++) {
            ImDrawVert& v = fgDL->VtxBuffer[vi];
            if (v.pos.y >= safeTop && v.pos.y <= safeBot)
                continue;
            float alpha = 1.0f;
            if (settingsFadeZoneTop > 0.0f) {
                float dTop = v.pos.y - settingsFadeTopY;
                if (dTop < settingsFadeZoneTop)
                    alpha = std::min(alpha, std::max(0.0f, dTop / settingsFadeZoneTop));
            }
            if (settingsFadeZoneBot > 0.0f) {
                float dBot = settingsFadeBottomY - v.pos.y;
                if (dBot < settingsFadeZoneBot)
                    alpha = std::min(alpha, std::max(0.0f, dBot / settingsFadeZoneBot));
            }
            if (alpha < 1.0f) {
                int a = (v.col >> IM_COL32_A_SHIFT) & 0xFF;
                a = static_cast<int>(a * alpha);
                v.col = (v.col & ~IM_COL32_A_MASK) | (static_cast<ImU32>(a) << IM_COL32_A_SHIFT);
            }
        }
    }

    ImGui::EndChild(); // ##SettingsPageScroll

    // Confirmation dialog for clearing z-tx history
    if (s_settingsState.confirm_clear_ztx) {
        if (BeginOverlayDialog(TR("confirm_clear_ztx_title"), &s_settingsState.confirm_clear_ztx, 480.0f, 0.94f)) {
            material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.6f, 0.0f, 1.0f));

            ImGui::Spacing();
            ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning1"));
            ImGui::Spacing();
            ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning2"));
            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            bool doCancel = false, doConfirm = false;
            material::DialogConfirmFooter(TR("cancel"), TrId("clear_anyway", "clear_ztx_btn").c_str(),
                                          true, doCancel, doConfirm);
            if (doCancel) {
                s_settingsState.confirm_clear_ztx = false;
            }
            if (doConfirm) {
                std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
                if (util::Platform::deleteFile(ztx_file)) {
                    Notifications::instance().success("Z-transaction history cleared");
                } else {
                    Notifications::instance().info("No history file found");
                }
                s_settingsState.confirm_clear_ztx = false;
            }
            EndOverlayDialog();
        }
    }

    // Confirmation dialog for deleting blockchain data
    if (s_settingsState.confirm_delete_blockchain) {
        if (BeginOverlayDialog(TR("confirm_delete_blockchain_title"), &s_settingsState.confirm_delete_blockchain, 500.0f, 0.94f)) {
            material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.3f, 0.3f, 1.0f));

            ImGui::Spacing();
            ImGui::TextWrapped("%s", TR("confirm_delete_blockchain_msg"));
            ImGui::Spacing();
            ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_delete_blockchain_safe"));
            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            bool doCancel = false, doConfirm = false;
            material::DialogConfirmFooter(TR("cancel"), TrId("delete_blockchain_confirm", "del_bc_btn").c_str(),
                                          true, doCancel, doConfirm);
            if (doCancel) {
                s_settingsState.confirm_delete_blockchain = false;
            }
            if (doConfirm) {
                if (app->supportsFullNodeLifecycleActions())
                    app->deleteBlockchainData();
                s_settingsState.confirm_delete_blockchain = false;
            }
            EndOverlayDialog();
        }
    }

    // Confirm: rescan blockchain. On a normal (full-history) node this restarts the daemon with
    // -rescan; on a bootstrapped/pruned node that would fail (pre-snapshot blocks are absent), so we
    // probe the lowest available block height and run a runtime rescanblockchain from a confirmed,
    // editable height instead.
    if (s_settingsState.confirm_rescan) {
        // Kick off the one-shot block-range probe the first frame the dialog is open.
        if (!s_settingsState.rescan_height_detecting && !s_settingsState.rescan_height_detected) {
            s_settingsState.rescan_height_detecting = true;
            app->detectLowestAvailableBlockHeight([](bool ok, int lowest, bool fullHistory) {
                s_settingsState.rescan_height_detecting = false;
                s_settingsState.rescan_height_detected = true;
                s_settingsState.rescan_full_history = (!ok) || fullHistory;
                s_settingsState.rescan_start_height = (ok && !fullHistory) ? lowest : 0;
            });
        }

        if (BeginOverlayDialog(TR("confirm_rescan_title"), &s_settingsState.confirm_rescan, 500.0f, 0.94f)) {
            material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));

            ImGui::Spacing();

            const bool detecting = s_settingsState.rescan_height_detecting;
            const bool bootstrapped = s_settingsState.rescan_height_detected && !s_settingsState.rescan_full_history;

            if (detecting) {
                ImGui::TextWrapped("%s", TR("rescan_detecting"));
            } else if (bootstrapped) {
                ImGui::TextWrapped("%s", TR("rescan_bootstrapped_msg"));
                ImGui::Spacing();
                ImGui::Text("%s", TR("rescan_from_height"));
                ImGui::SetNextItemWidth(160.0f);
                ImGui::InputInt("##rescanHeight", &s_settingsState.rescan_start_height);
                if (s_settingsState.rescan_start_height < 0) s_settingsState.rescan_start_height = 0;
            } else {
                ImGui::TextWrapped("%s", TR("confirm_rescan_msg"));
                ImGui::Spacing();
                ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_rescan_safe"));
            }

            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
            if (material::TactileButton(TrId("cancel", "rescan_cancel").c_str(), ImVec2(btnW, 40))) {
                s_settingsState.confirm_rescan = false;
            }
            ImGui::SameLine();
            ImGui::BeginDisabled(detecting);
            if (material::TactileButton(TrId("rescan", "rescan_confirm").c_str(), ImVec2(btnW, 40))) {
                if (bootstrapped) {
                    app->runtimeRescan(s_settingsState.rescan_start_height);
                } else {
                    app->rescanBlockchain();
                }
                s_settingsState.confirm_rescan = false;
            }
            ImGui::EndDisabled();
            EndOverlayDialog();
        }
    }

    // Confirm: repair wallet (-zapwallettxes=2 — wipe & rebuild wallet tx records, then rescan)
    if (s_settingsState.confirm_repair_wallet) {
        if (BeginOverlayDialog(TR("confirm_repair_wallet_title"), &s_settingsState.confirm_repair_wallet, 500.0f, 0.94f)) {
            material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));

            ImGui::Spacing();
            ImGui::TextWrapped("%s", TR("confirm_repair_wallet_msg"));
            ImGui::Spacing();
            ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_repair_wallet_safe"));
            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            bool doCancel = false, doConfirm = false;
            material::DialogConfirmFooter(TrId("cancel", "repair_wallet_cancel").c_str(),
                                          TrId("repair_wallet", "repair_wallet_confirm").c_str(),
                                          false, doCancel, doConfirm);
            if (doCancel) {
                s_settingsState.confirm_repair_wallet = false;
            }
            if (doConfirm) {
                app->repairWallet();
                s_settingsState.confirm_repair_wallet = false;
            }
            EndOverlayDialog();
        }
    }

    // Confirm: reinstall the bundled daemon binary (stop → overwrite → restart)
    if (s_settingsState.confirm_reinstall_daemon) {
        if (BeginOverlayDialog(TR("confirm_reinstall_daemon_title"), &s_settingsState.confirm_reinstall_daemon, 500.0f, 0.94f)) {
            material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));

            ImGui::Spacing();
            ImGui::TextWrapped("%s", TR("confirm_reinstall_daemon_msg"));
            ImGui::Spacing();
            ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_reinstall_daemon_safe"));
            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            bool doCancel = false, doConfirm = false;
            material::DialogConfirmFooter(TrId("cancel", "reinstall_daemon_cancel").c_str(),
                                          TrId("daemon_install_bundled", "reinstall_daemon_confirm").c_str(),
                                          false, doCancel, doConfirm);
            if (doCancel) {
                s_settingsState.confirm_reinstall_daemon = false;
            }
            if (doConfirm) {
                app->reinstallBundledDaemon();
                s_settingsState.daemon_info_loaded = false;  // refresh the panel after the swap
                s_settingsState.confirm_reinstall_daemon = false;
            }
            EndOverlayDialog();
        }
    }

    // Confirm: restart daemon (briefly drops the connection to apply changed options)
    if (s_settingsState.confirm_restart_daemon) {
        if (BeginOverlayDialog(TR("confirm_restart_daemon_title"), &s_settingsState.confirm_restart_daemon, 500.0f, 0.94f)) {
            material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));

            ImGui::Spacing();
            ImGui::TextWrapped("%s", TR("confirm_restart_daemon_msg"));
            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            bool doCancel = false, doConfirm = false;
            material::DialogConfirmFooter(TrId("cancel", "restartd_cancel").c_str(),
                                          TrId("settings_restart_daemon", "restartd_confirm").c_str(),
                                          false, doCancel, doConfirm);
            if (doCancel) {
                s_settingsState.confirm_restart_daemon = false;
            }
            if (doConfirm) {
                s_settingsState.debug_cats_dirty = false;
                app->restartDaemon();
                s_settingsState.confirm_restart_daemon = false;
            }
            EndOverlayDialog();
        }
    }

    // Confirm: lite wallet re-download blocks (rescan from the lite server — long but safe)
    if (s_settingsState.confirm_lite_redownload) {
        if (BeginOverlayDialog(TR("confirm_lite_redownload_title"), &s_settingsState.confirm_lite_redownload, 500.0f, 0.94f)) {
            material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));

            ImGui::Spacing();
            ImGui::TextWrapped("%s", TR("confirm_lite_redownload_msg"));
            ImGui::Spacing();
            ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_lite_redownload_safe"));
            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            bool doCancel = false, doConfirm = false;
            material::DialogConfirmFooter(TrId("cancel", "lite_redl_cancel").c_str(),
                                          TrId("lite_redownload_blocks", "lite_redl_confirm").c_str(),
                                          false, doCancel, doConfirm);
            if (doCancel) {
                s_settingsState.confirm_lite_redownload = false;
            }
            if (doConfirm) {
                if (auto* lite = app->liteWallet()) lite->startRescan();
                s_settingsState.confirm_lite_redownload = false;
            }
            EndOverlayDialog();
        }
    }

}

void SweepOpenDebugGate(bool open) {
    s_settingsState.debug_gate_open = open;
    if (open) {
        s_settingsState.debug_gate_buf[0] = '\0';
        s_settingsState.debug_gate_err.clear();
        s_settingsState.debug_gate_verifying = false;
    }
}

} // namespace ui
} // namespace dragonx
