// 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/chat_tab.h"          // RenderChatSettingsControls (shared Chat & Contacts controls)
#include "../windows/console_tab.h"
#include "../../util/i18n.h"
#include "../../util/platform.h"
#include "../../util/seed_phrase.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/settings_controls.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 stratum_host = false;          // O2: host a RandomX stratum pool from the node (v1.3.0+)
    char stratum_allowip[64] = "";      // -stratumallowip subnet (blank = loopback only)
    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
    int  current_tab = 0;                  // active settings category tab (see SettingsTab enum)
    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 words in a (seed) buffer — used to validate/guide restore input. Normalizes exotic Unicode
// whitespace (NBSP etc.) first so the count matches the phrase actually submitted (shared with the
// first-run restore gate via util::seed_phrase).
static int liteSeedWordCount(const char* s) {
    return dragonx::util::seedPhraseWordCount(
        dragonx::util::normalizeSeedPhrase(s ? std::string(s) : std::string()));
}

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;
            // Normalize (fold NBSP/exotic whitespace to plain spaces) so an NBSP-pasted phrase the
            // gate counted as 24 words also restores correctly at the backend.
            input.request.restoreRequest.seedPhrase = dragonx::util::normalizeSeedPhrase(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 = TR("lite_enter_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 (!dragonx::util::isCompleteRecoveryPhrase(words)) {
            char seedBuf[128];
            snprintf(seedBuf, sizeof(seedBuf), TR("lite_enter_all_seed_words"), words);
            s_settingsState.lite_lifecycle_status = seedBuf;
            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()
                ? TR("lite_could_not_start")
                : 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 = TR("lite_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();
    s_settingsState.stratum_host = settings->getStratumHost();
    std::snprintf(s_settingsState.stratum_allowip, sizeof(s_settingsState.stratum_allowip), "%s",
                  settings->getStratumAllowIp().c_str());
    // 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);
    settings->setStratumHost(s_settingsState.stratum_host);
    settings->setStratumAllowIp(s_settingsState.stratum_allowip);
    // 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::SameLine(0, Layout::spacingLg());
    // Console behavior (not a GPU effect): focus the command input when the tab opens. Bound straight to
    // settings — the App reads it at the page transition; no ConsoleTab static needed.
    bool autoFocus = app->settings()->getConsoleAutoFocus();
    if (ImGui::Checkbox(TrId("console_auto_focus", "con_autofocus").c_str(), &autoFocus)) {
        app->settings()->setConsoleAutoFocus(autoFocus);
        app->settings()->save();
    }
    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("console_toggle_auto_focus"));
    ImGui::BeginDisabled(s_settingsState.low_spec_mode);
}

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

// A full-card-width, left-aligned, solid button (icon + label) drawn at an explicit (x,y).
// Used by the side-by-side "column card" tabs (Backup, Wallet) where content is positioned
// manually because ImGui's Indent (which GlassCardScope uses) is window-relative.
static bool renderCardButton(ImDrawList* dl, float x, float y, float w, float h,
                             const char* id, const char* label, const char* icon) {
    using namespace material;
    ImGui::SetCursorScreenPos(ImVec2(x, y));
    ImFont* lf = Type().button();
    ImFont* icf = Type().iconSmall();
    const float dpp = Layout::dpiScale();
    const float padX = 12.0f * dpp, ig = 6.0f * dpp;
    const float bh = h;
    const bool pressed = ImGui::InvisibleButton(id, ImVec2(w, bh));
    const bool hov = ImGui::IsItemHovered();
    if (hov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
    const ImVec2 pmin(x, y), pmax(x + w, y + bh);
    const float round = 7.0f * dpp;   // match ActionButton chips (mockup .btn radius: 7px)
    dl->AddRectFilled(pmin, pmax, WithAlpha(OnSurface(), hov ? 30 : 20), round);
    dl->AddRect(pmin, pmax, WithAlpha(OnSurface(), 48), round, 0, 1.0f);
    const ImU32 fg = ImGui::GetColorU32(OnSurface());
    dl->PushClipRect(pmin, pmax, true);
    float tx = x + padX;
    if (icon && icon[0] && icf) {
        dl->AddText(icf, icf->LegacySize, ImVec2(tx, y + (bh - icf->LegacySize) * 0.5f), fg, icon);
        tx += icf->CalcTextSizeA(icf->LegacySize, FLT_MAX, 0, icon).x + ig;
    }
    dl->AddText(lf, lf->LegacySize, ImVec2(tx, y + (bh - lf->LegacySize) * 0.5f), fg, label);
    dl->PopClipRect();
    return pressed;
}

// ---- Category tabs (top-level settings navigation) -------------------------
enum SettingsTab { TAB_APPEARANCE = 0, TAB_WALLET, TAB_BACKUP, TAB_NODE, TAB_EXPLORER, TAB_CHAT, TAB_ABOUT, TAB_COUNT };

// Pinned horizontal category tab bar, drawn above the settings scroll region. Each category is a
// pill; the active one gets an accent fill. Advances the ImGui cursor past the bar + a divider so
// the scrollable content begins below it.
static void renderSettingsTabBar(float availWidth) {
    using namespace material;
    struct T { int id; const char* label; const char* idstr; };
    const T tabs[] = {
        {TAB_APPEARANCE, TR("grpa_tab_appearance"),    "##stabA"}, {TAB_WALLET, TR("grpa_tab_wallet"),          "##stabW"},
        {TAB_BACKUP,     TR("grpa_tab_backup_data"), "##stabB"}, {TAB_NODE,   TR("grpa_tab_node_security"), "##stabN"},
        {TAB_EXPLORER,   TR("grpa_tab_explorer"),      "##stabE"}, {TAB_CHAT,   TR("grpa_tab_chat"),            "##stabC"},
        {TAB_ABOUT,      TR("grpa_tab_about"),         "##stabT"},
    };
    ImDrawList* dl = ImGui::GetWindowDrawList();
    ImFont* f = Type().body2();
    const float dp = Layout::dpiScale();
    const float padX = 13.0f * dp, padY = 7.0f * dp, gap = 6.0f * dp, rnd = 8.0f * dp;
    const float h = f->LegacySize + padY * 2.0f;
    const ImVec2 origin = ImGui::GetCursorScreenPos();
    float x = origin.x, y = origin.y;
    for (const T& t : tabs) {
        ImVec2 ts = f->CalcTextSizeA(f->LegacySize, FLT_MAX, 0, t.label);
        float w = ts.x + padX * 2.0f;
        if (x > origin.x && x + w > origin.x + availWidth) { x = origin.x; y += h + gap; }  // wrap
        ImGui::SetCursorScreenPos(ImVec2(x, y));
        if (ImGui::InvisibleButton(t.idstr, ImVec2(w, h))) s_settingsState.current_tab = t.id;
        const bool hovered = ImGui::IsItemHovered();
        const bool active  = (s_settingsState.current_tab == t.id);
        if (active)       dl->AddRectFilled(ImVec2(x, y), ImVec2(x + w, y + h), WithAlpha(Primary(), 34), rnd);
        else if (hovered) dl->AddRectFilled(ImVec2(x, y), ImVec2(x + w, y + h), IM_COL32(255, 255, 255, 12), rnd);
        dl->AddText(f, f->LegacySize, ImVec2(x + (w - ts.x) * 0.5f, y + (h - ts.y) * 0.5f),
                    ImGui::GetColorU32((active || hovered) ? OnSurface() : OnSurfaceMedium()), t.label);
        x += w + gap;
    }
    const float bottom = y + h;
    dl->AddLine(ImVec2(origin.x, bottom + 5.0f * dp), ImVec2(origin.x + availWidth, bottom + 5.0f * dp),
                ImGui::GetColorU32(Divider()), 1.0f);
    ImGui::SetCursorScreenPos(ImVec2(origin.x, bottom + 12.0f * dp));
}

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;

    // Settings fills the full content width (the global content-max-width cap is disabled).
    float settingsLeftOffset = 0.0f;
    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);
    (void)inputW;  // used by some sections; may be unused depending on active tab

    // Category tab bar — pinned above the scrollable content area (not part of the scroll).
    renderSettingsTabBar(availWidth);

    // Scrollable content area — NoBackground matches other tabs
    if (settingsLeftOffset > 0.0f) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + settingsLeftOffset);
    ImGui::BeginChild("##SettingsPageScroll", ImVec2(settingsLeftOffset > 0.0f ? availWidth + scrollbarMargin : 0.0f, 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;
    glassSpec.fillAlpha   = 26;   // lift the settings cards off the background (closer to the mockup's flat cards)
    glassSpec.borderAlpha = 50;   // crisper, more defined card border (mockup uses a visible 1px line)
    ImFont* capFont = Type().caption();
    ImFont* body2   = Type().body2();
    ImFont* sub1    = Type().subtitle1();

    // ====================================================================
    // APPEARANCE — two stacked cards: THEME & LANGUAGE (2x2 dropdown grid)
    //   then SCALE & EFFECTS (font scale + effect toggles + Advanced sliders).
    // ====================================================================
    if (s_settingsState.current_tab == TAB_APPEARANCE) {
        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;
            }
        }
        (void)active_is_custom;

        // --- 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) ---
        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 + TR("grpa_invalid_suffix");
                        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();
                    }
                }
            }
        };

        // ============================================================
        // Card 1 — THEME & LANGUAGE (2x2 grid of labeled dropdowns)
        // ============================================================
        {
            material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
            Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("theme_language"));
            ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
            float contentW = availWidth - pad * 2;
            float cellGap  = Layout::spacingLg();
            bool  twoCol   = contentW >= 460.0f * dp;   // drop to a single column when too narrow (high font scale)
            int   cols     = twoCol ? 2 : 1;
            float colW     = std::max(160.0f, twoCol ? (contentW - cellGap) * 0.5f : contentW);
            float baseX    = ImGui::GetCursorScreenPos().x;

            const char* cellLabels[4] = { TR("theme"), TR("balance_layout"), TR("language"), TR("clock_format") };

            ImGui::PushFont(body2);
            float lblW = 0.0f;   // label column — mockup puts the label BESIDE the control (.row), not above
            for (int i = 0; i < 4; ++i) lblW = std::max(lblW, ImGui::CalcTextSize(cellLabels[i]).x);
            lblW += Layout::spacingMd();
            float rowTop = ImGui::GetCursorScreenPos().y;
            float rowBottom = rowTop;
            for (int i = 0; i < 4; ++i) {
                int col = i % cols;
                if (col == 0 && i > 0) rowTop = rowBottom;          // start a new grid row
                float cx = baseX + col * (colW + cellGap);

                // Field label on the left, control filling the rest of the cell (mockup .row layout).
                ImGui::SetCursorScreenPos(ImVec2(cx, rowTop));
                ImGui::AlignTextToFramePadding();
                ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(OnSurfaceMedium()));
                ImGui::TextUnformatted(cellLabels[i]);
                ImGui::PopStyleColor();
                ImGui::SetCursorScreenPos(ImVec2(cx + lblW, rowTop));
                ImGui::SetNextItemWidth(colW - lblW);

                switch (i) {
                    case 0:  // Theme
                        if (ImGui::BeginCombo("##Theme", active_preview.c_str())) { renderThemeComboPopup(); ImGui::EndCombo(); }
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_theme_hotkey"));
                        break;
                    case 1:  // Balance layout
                        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(l.id); app->settings()->save(); }
                                }
                                if (selected) ImGui::SetItemDefaultFocus();
                            }
                            ImGui::EndCombo();
                        }
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_layout_hotkey"));
                        break;
                    case 2:  // Language
                        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"));
                        break;
                    case 3: {  // Clock format
                        int cf = app->settings() ? app->settings()->getTimeFormat() : 0;
                        const char* cfItems[] = { TR("chat_ts_24h"), TR("chat_ts_12h") };
                        if (ImGui::Combo("##ClockFormat", &cf, cfItems, 2) && app->settings()) {
                            app->settings()->setTimeFormat(cf);
                            app->settings()->save();
                        }
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_clock_format"));
                        break;
                    }
                }

                float cellBottom = ImGui::GetCursorScreenPos().y;
                rowBottom = (col == 0) ? cellBottom : std::max(rowBottom, cellBottom);
                if (col == cols - 1 || i == 3) {
                    ImGui::SetCursorScreenPos(ImVec2(baseX, rowBottom + 11.0f * dp));
                    rowBottom = ImGui::GetCursorScreenPos().y;
                }
            }
            ImGui::PopFont();

            // Rescan the theme folder — minor action, tucked below the grid.
            ImGui::SetCursorScreenPos(ImVec2(baseX, rowBottom));
            if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) {
                schema::SkinManager::instance().refresh();
                Notifications::instance().info(TR("settings_theme_refreshed"));
            }
            if (ImGui::IsItemHovered())
                material::Tooltip(TR("tt_scan_themes"), schema::SkinManager::getUserSkinsDirectory().c_str());
        }

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

        // ============================================================
        // Card 2 — SCALE & EFFECTS (font scale + effect toggles + Advanced sliders)
        // ============================================================
        {
            material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
            Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("scale_effects"));
            ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
            float contentW = availWidth - pad * 2;

            // --- Font Scale slider ---
            {
                ImGui::PushFont(body2);
                ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(OnSurfaceMedium()));
                ImGui::TextUnformatted(TR("font_scale"));
                ImGui::PopStyleColor();
                float fontSliderW = std::min(std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, contentW), 360.0f * dp);
                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::spacingMd()));

            // --- Effect toggles (always visible, horizontal wrapping flow) ---
            {
                ImGui::PushFont(body2);
                const float efFh = ImGui::GetFrameHeight();
                const float efInner = ImGui::GetStyle().ItemInnerSpacing.x;
                float efX = 0.0f; bool efFirst = true;
                auto efFlow = [&](const char* label) {
                    const float w = efFh + efInner + ImGui::CalcTextSize(label).x;
                    if (efFirst) { efFirst = false; efX = w; }
                    else if (efX + Layout::spacingLg() + w <= contentW) { ImGui::SameLine(0, Layout::spacingLg()); efX += Layout::spacingLg() + w; }
                    else { efX = w; }
                };

                efFlow(TR("low_spec_mode"));
                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"));

                efFlow(TR("simple_background"));
                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"));

                efFlow(TR("reduce_motion"));
                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);

                efFlow(TR("console_scanline"));
                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"));

                efFlow(TR("theme_effects"));
                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"));

                efFlow(TR("animate_avatars"));
                {
                    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"));
                }

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

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

            // --- Collapsible: Advanced Effects... (console colors + 2x2 opacity/blur sliders) ---
            material::CollapsibleHeader(dl, "##EffectsToggle", TR("advanced_effects"),
                                        s_settingsState.effects_expanded, contentW,
                                        body2, OnSurfaceMedium());

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

                ImGui::BeginDisabled(s_settingsState.low_spec_mode);

                // Console output color toggles (own row — no GPU cost, enabled even in low-spec).
                // renderConsoleColorToggles() temporarily End/BeginDisabled()s so its own checkboxes
                // stay enabled — it MUST be called while exactly one BeginDisabled is active.
                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::min(std::max(effCtrlMinW, halfW), 360.0f * dp);
                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
        }
    }

    // ====================================================================
    // WALLET — card (privacy/daemon toggles + collapsible tools)
    // ====================================================================
    if (s_settingsState.current_tab == TAB_WALLET) {
        const bool showDaemonOptions = app->supportsFullNodeLifecycleActions();

        // Two side-by-side glass cards: OPTIONS (toggles) | DIAGNOSTICS (log + tools).
        const float ccGap   = Layout::cardGap();
        const float ccW     = (availWidth - ccGap) * 0.5f;
        const float cw      = ccW - pad * 2;
        const float ccTop   = ImGui::GetCursorScreenPos().y;
        const float ccBaseX = ImGui::GetCursorScreenPos().x;
        float ccBottom = ccTop;

        // One foreground channel for both cards; panels painted afterwards at equal (tallest) height.
        float cardBot[2] = { ccTop, ccTop };
        dl->ChannelsSplit(2);
        dl->ChannelsSetCurrent(1);
        auto cardHeader = [&](int col, const char* header) -> float {
            const float cx = ccBaseX + col * (ccW + ccGap);
            ImGui::SetCursorScreenPos(ImVec2(cx + pad, ccTop + pad));
            Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), header);
            return ImGui::GetCursorScreenPos().y + Layout::spacingMd();
        };
        auto cardClose = [&](int col, float lastBottom) {
            cardBot[col] = lastBottom + pad;
            ccBottom = std::max(ccBottom, cardBot[col]);
        };

        ImGui::PushFont(body2);
        const float fh = ImGui::GetFrameHeight();                 // checkbox row height
        const float bh = ImGui::GetFrameHeight() + 12.0f * dp;    // taller, airier buttons (match mockup)
        const float gp = Layout::spacingSm();                     // roomier gap

        // ---- Card 0: OPTIONS (checkboxes in a 2-column grid — mockup .chks.two) ----
        {
            const float cx = ccBaseX + pad;
            const float col2W = (cw - Layout::spacingLg()) * 0.5f;
            float rowY = cardHeader(0, TR("wallet_options_hdr"));
            int c = 0; float last = rowY;
            auto CB = [&](const std::string& id, bool* val) -> bool {
                ImGui::SetCursorScreenPos(ImVec2(cx + c * (col2W + Layout::spacingLg()), rowY));
                const bool changed = ImGui::Checkbox(id.c_str(), val);
                last = rowY + fh;
                if (c == 1) { rowY += fh + gp; c = 0; } else { c = 1; }
                return changed;
            };
            CB(TrId("save_z_transactions", "save_ztx"), &s_settingsState.save_ztxs);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_save_ztx"));
            CB(TrId("auto_shield", "auto_shld"), &s_settingsState.auto_shield);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_auto_shield"));
            CB(TrId("use_tor", "tor"), &s_settingsState.use_tor);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_tor"));
            if (showDaemonOptions) {
                if (CB(TrId("keep_daemon", "keep_dmn"), &s_settingsState.keep_daemon_running))
                    saveSettingsPageState(app->settings());
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_keep_daemon"));
                if (CB(TrId("stop_external", "stop_ext"), &s_settingsState.stop_external_daemon))
                    saveSettingsPageState(app->settings());
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_stop_external"));
                // (Stratum pool hosting lives in the Node & Security tab — it's a node feature.)
            }
            if (CB(TrId("verbose_logging", "verbose"), &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"));

            // O1: node coinbase auto-shield status (v1.3.0+). Rendered as a full-width note BELOW the
            // checkbox grid (not wedged between checkboxes) with friendly wording — the daemon's raw
            // technical reason is available on hover. Nothing renders on pre-1.3.0 daemons (never probed).
            if (app && app->daemonAutoShieldProbed() &&
                (app->daemonAutoShieldActive() || !app->daemonAutoShieldDisabledReason().empty())) {
                ImGui::SetCursorScreenPos(ImVec2(cx, last + gp));
                ImGui::PushTextWrapPos((cx + cw) - ImGui::GetWindowPos().x);   // wrap to the card content width
                if (app->daemonAutoShieldActive()) {
                    ImGui::TextColored(ImVec4(0.40f, 0.78f, 0.40f, 1.0f), "%s", TR("autoshield_by_node"));
                    if (!app->daemonAutoShieldAddress().empty())
                        ImGui::TextDisabled("    %s", app->daemonAutoShieldAddress().c_str());
                } else {
                    // Friendly, actionable wording. The seed-not-recoverable case is the common one and has a
                    // clear fix (back up the seed); anything else gets a generic line. Raw daemon text on hover.
                    ImGui::TextDisabled("%s", app->daemonAutoShieldSeedRecoverable()
                                                  ? TR("autoshield_off_generic")
                                                  : TR("autoshield_off_backup_seed"));
                    if (ImGui::IsItemHovered())
                        material::Tooltip("%s", app->daemonAutoShieldDisabledReason().c_str());
                }
                ImGui::PopTextWrapPos();
                last = ImGui::GetCursorScreenPos().y;   // grow the card to include the note
            }
            cardClose(0, last);
        }

        // ---- Card 1: DIAGNOSTICS + Tools & Actions (2-column button grids) ----
        {
            const float cx = ccBaseX + (ccW + ccGap) + pad;
            const float col2W = (cw - Layout::spacingLg()) * 0.5f;
            float rowY = cardHeader(1, TR("wallet_diagnostics_hdr"));
            int c = 0; float last = rowY;
            auto BTN = [&](const char* id, const char* label, const char* icon) -> bool {
                const float bx = cx + c * (col2W + Layout::spacingLg());
                const bool p = renderCardButton(dl, bx, rowY, col2W, bh, id, label, icon);
                last = rowY + bh;
                if (c == 1) { rowY += bh + gp; c = 0; } else { c = 1; }
                return p;
            };
            auto rowBreak = [&]() { if (c == 1) { rowY += bh + gp; c = 0; } };

            if (BTN("##wlog", TR("settings_open_log_folder"), ICON_MD_FOLDER))
                dragonx::util::Platform::openFolder(dragonx::util::Platform::getObsidianDragonDir());
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_log_folder"));
            if (BTN("##wdiag", TR("settings_copy_diagnostics"), ICON_MD_CONTENT_COPY)) {
                ImGui::SetClipboardText(app->buildDiagnosticsReport().c_str());
                ui::Notifications::instance().info(TR("settings_diagnostics_copied"), 4.0f);
            }
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_copy_diagnostics"));
            rowBreak();

            rowY += Layout::spacingSm();
            ImGui::SetCursorScreenPos(ImVec2(cx, rowY));
            Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("tools_actions_hdr"));
            rowY = ImGui::GetCursorScreenPos().y + Layout::spacingMd();
            c = 0;

            if (BTN("##waddr", TR("settings_address_book"), ICON_MD_CONTACTS))
                app->setCurrentPage(ui::NavPage::Contacts);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_address_book"));
            if (BTN("##wval", TR("settings_validate_address"), ICON_MD_CHECK_CIRCLE))
                ValidateAddressDialog::show();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_validate"));
            if (BTN("##wreq", TR("settings_request_payment"), ICON_MD_QR_CODE))
                RequestPaymentDialog::show();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_request_payment"));
            if (BTN("##wshield", TR("settings_shield_mining"), ICON_MD_SHIELD))
                ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_shield_mining"));
            if (BTN("##wmerge", TR("settings_merge_to_address"), ICON_MD_CALL_MERGE))
                ShieldDialog::showMerge();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_merge"));
            if (BTN("##wclear", TR("settings_clear_ztx"), ICON_MD_DELETE_SWEEP))
                s_settingsState.confirm_clear_ztx = true;
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_clear_ztx"));
            rowBreak();
            cardClose(1, last);
        }

        // Paint both cards at the same (tallest) height, then merge the channels.
        {
            const float eq = std::max(cardBot[0], cardBot[1]);
            dl->ChannelsSetCurrent(0);
            for (int col = 0; col < 2; ++col) {
                const float cx = ccBaseX + col * (ccW + ccGap);
                material::DrawGlassPanel(dl, ImVec2(cx, ccTop), ImVec2(cx + ccW, eq), glassSpec);
            }
            dl->ChannelsMerge();
        }

        ImGui::PopFont();
        // Reserve the full multi-card footprint with a Dummy so the scroll region grows to include
        // the manually-positioned cards (a bare SetCursorScreenPos past content warns in ImGui).
        ImGui::SetCursorScreenPos(ImVec2(ccBaseX, ccTop));
        ImGui::Dummy(ImVec2(availWidth, (ccBottom - ccTop) + Layout::spacingSm()));
    }

    // ====================================================================
    // BACKUP & DATA — card (own category tab; split out of Wallet)
    // ====================================================================
    if (s_settingsState.current_tab == TAB_BACKUP) {
        const bool fullNode = app->supportsFullNodeLifecycleActions();

        // Three side-by-side glass cards, each with its header inside (mockup-style grouping).
        // Content is positioned manually (SetCursorScreenPos) because ImGui's Indent — which
        // GlassCardScope relies on — is window-relative and would pull offset columns back to x0.
        const float ccGap  = Layout::cardGap();
        const int   ccN    = 3;
        const float ccW    = (availWidth - ccGap * (ccN - 1)) / (float)ccN;
        const float cw     = ccW - pad * 2;
        const float ccTop  = ImGui::GetCursorScreenPos().y;
        const float ccBaseX = ImGui::GetCursorScreenPos().x;
        float ccBottom = ccTop;

        ImGui::PushFont(body2);
        const float bh  = ImGui::GetFrameHeight() + 12.0f * dp;   // taller, airier buttons (match mockup)
        const float bgp = Layout::spacingSm();                    // roomier gap between buttons

        // A full-card-width, left-aligned, solid button drawn at an explicit (x,y).
        auto cardBtn = [&](float x, float y, float w, const char* id, const char* label, const char* icon) -> bool {
            return renderCardButton(dl, x, y, w, bh, id, label, icon);
        };
        // All cards render onto one foreground channel; the glass panels are painted afterwards at a
        // single equal height (the tallest card) so side-by-side cards match — mockup grid-stretch look.
        float cardBot[3] = { ccTop, ccTop, ccTop };
        dl->ChannelsSplit(2);
        dl->ChannelsSetCurrent(1);
        auto cardHeader = [&](int col, const char* header) -> float {
            const float cx = ccBaseX + col * (ccW + ccGap);
            float cy = ccTop + pad;
            ImGui::SetCursorScreenPos(ImVec2(cx + pad, cy));
            Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), header);
            return ImGui::GetCursorScreenPos().y + Layout::spacingMd();
        };
        auto cardClose = [&](int col, float lastBottom) {
            cardBot[col] = lastBottom + pad;
            ccBottom = std::max(ccBottom, cardBot[col]);
        };

        // ---- Card 0: Import & Restore ----
        {
            const float cx = ccBaseX + pad;
            float cy = cardHeader(0, TR("backup_col_import"));
            float last = cy;
            if (cardBtn(cx, cy, cw, "##imp_key", TR("settings_import_key"), ICON_MD_KEY)) app->showImportKeyDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_import_key"));
            last = cy + bh; cy += bh + bgp;
            if (cardBtn(cx, cy, cw, "##imp_vk", TR("settings_import_viewkey"), ICON_MD_VISIBILITY)) app->showImportViewingKeyDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_import_viewkey"));
            last = cy + bh;
            if (fullNode) {
                cy += bh + bgp;
                if (cardBtn(cx, cy, cw, "##wallets", TR("wallets_button"), ICON_MD_ACCOUNT_BALANCE_WALLET)) ui::WalletsDialog::show(app);
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_wallets_button"));
                last = cy + bh; cy += bh + bgp;
                if (cardBtn(cx, cy, cw, "##bootstrap", TR("download_bootstrap"), ICON_MD_CLOUD_DOWNLOAD)) BootstrapDownloadDialog::show(app);
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_download_bootstrap"));
                last = cy + bh;
            }
            cardClose(0, last);
        }

        // ---- Card 1: Backup ----
        {
            const float cx = ccBaseX + (ccW + ccGap) + pad;
            float cy = cardHeader(1, TR("backup_col_backup"));
            float last = cy;
            if (fullNode) {
                if (cardBtn(cx, cy, cw, "##seed", TR("seed_backup_button"), ICON_MD_VPN_KEY)) app->showSeedBackupDialog();
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_backup"));
                last = cy + bh; cy += bh + bgp;
            }
            if (cardBtn(cx, cy, cw, "##backup", TR("settings_backup"), ICON_MD_BACKUP)) app->showBackupDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_backup"));
            last = cy + bh;
            if (fullNode) {
                cy += bh + bgp;
                const bool migrateGlow = app->isPreSeedWallet();
                if (cardBtn(cx, cy, cw, "##migrate", TR("seed_migrate_button"), ICON_MD_SWAP_HORIZ)) app->showSeedMigrationDialog();
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_migrate"));
                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);
                    for (int g = 3; g >= 1; --g) {
                        const float e = (float)g * 2.2f * gdp;
                        const int a = (int)((70.0f + pulse * 95.0f) / (float)g);
                        dl->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);
                    }
                }
                last = cy + bh; cy += bh + bgp;
                if (cardBtn(cx, cy, cw, "##wizard", TR("setup_wizard"), ICON_MD_AUTO_FIX_HIGH)) app->restartWizard();
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_wizard"));
                last = cy + bh;
            }
            cardClose(1, last);
        }

        // ---- Card 2: Export ----
        {
            const float cx = ccBaseX + (ccW + ccGap) * 2.0f + pad;
            float cy = cardHeader(2, TR("backup_col_export"));
            float last = cy;
            if (cardBtn(cx, cy, cw, "##exp_key", TR("settings_export_key"), ICON_MD_LOGOUT)) app->showExportKeyDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_export_key"));
            last = cy + bh; cy += bh + bgp;
            if (cardBtn(cx, cy, cw, "##exp_all", TR("settings_export_all"), ICON_MD_ARCHIVE)) ExportAllKeysDialog::show();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_export_all"));
            last = cy + bh; cy += bh + bgp;
            if (cardBtn(cx, cy, cw, "##exp_csv", TR("settings_export_csv"), ICON_MD_DESCRIPTION)) ExportTransactionsDialog::show();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_export_csv"));
            last = cy + bh;
            cardClose(2, last);
        }

        // Paint all three cards at the same (tallest) height, then merge the channels.
        {
            const float eq = std::max({cardBot[0], cardBot[1], cardBot[2]});
            dl->ChannelsSetCurrent(0);
            for (int col = 0; col < 3; ++col) {
                const float cx = ccBaseX + col * (ccW + ccGap);
                material::DrawGlassPanel(dl, ImVec2(cx, ccTop), ImVec2(cx + ccW, eq), glassSpec);
            }
            dl->ChannelsMerge();
        }

        ImGui::PopFont();
        // Reserve the full multi-card footprint with a Dummy so the scroll region grows to include
        // the manually-positioned cards (a bare SetCursorScreenPos past content warns in ImGui).
        ImGui::SetCursorScreenPos(ImVec2(ccBaseX, ccTop));
        ImGui::Dummy(ImVec2(availWidth, (ccBottom - ccTop) + Layout::spacingSm()));
    }

    // ====================================================================
    // NODE & SECURITY — card
    // ====================================================================
    if (s_settingsState.current_tab == TAB_NODE) {
        // Two side-by-side glass cards (NODE/SECURITY | DAEMON BINARY), drawn manually because
        // GlassCardScope's Indent is window-relative and can't offset the right card. All the
        // column *content* below is unchanged; only the card wrapper differs.
        const float ndTop   = ImGui::GetCursorScreenPos().y;
        const float ndBaseX = ImGui::GetCursorScreenPos().x;
        bool  ndTwoCol = false;
        float ndColW = 0.0f, ndColGap = 0.0f, ndLeftBottom = 0.0f, ndRightBottom = 0.0f, ndSingleBottom = ndTop;
        dl->ChannelsSplit(2);
        dl->ChannelsSetCurrent(1);
        ImGui::SetCursorScreenPos(ImVec2(ndBaseX, ndTop + pad));
        ImGui::Indent(pad);

        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) {
                // Narrow (two-column) card: the encrypt buttons, auto-lock and PIN
                // controls can't all share one row, so wrap groups onto fresh rows.
                const bool secNarrow = secColW < 740.0f * Layout::dpiScale();
                // 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, or wraps below
                        // when the column is too narrow for three buttons + status.
                        if (secNarrow) {
                            ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                            ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
                        } else {
                            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; }
                    }
                    // Auto-lock gets its own full-width row (label left, dropdown filling — mockup).
                    (void)secNarrow;
                    if (includeRpcEncrypt) ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                    ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
                    ImGui::AlignTextToFramePadding();
                    Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("settings_auto_lock"));
                    const float alLblW = ImGui::CalcTextSize(TR("settings_auto_lock")).x;
                    ImGui::SameLine(0, Layout::spacingMd());
                    ImGui::SetNextItemWidth(std::max(comboW, secColW - alLblW - Layout::spacingMd()));
                    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"));

                    // 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::Dummy(ImVec2(0, Layout::spacingXs()));
                        ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
                        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::Dummy(ImVec2(0, Layout::spacingSm()));
                        ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
                        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 (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_lifecycle_toggle"));

                    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 (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_lifecycle_op"));

                        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 (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_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);
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_restore_seed"));
                            // 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 * dp, liteInputW));
                            ImGui::InputInt("##LiteRestoreBirthday", &s_settingsState.lite_restore_birthday);
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_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 * dp, liteInputW));
                            ImGui::InputInt("##LiteRestoreAccount", &s_settingsState.lite_restore_account);
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_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);
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_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);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_lifecycle_pass"));

                        // 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 (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_lite_lifecycle_run"));
                        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 (material::ActionButton("##liteopendir", TR("settings_open_data_dir"), ICON_MD_FOLDER_OPEN, material::ActionTier::Secondary)) {
                        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().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("lite_backup_keys"));

                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        if (material::ActionButton("##LiteExportSeed", TR("lite_show_seed"), ICON_MD_VPN_KEY, material::ActionTier::Secondary)) {
                            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
                        }
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_show_seed"));
                        ImGui::SameLine(0, Layout::spacingSm());
                        if (material::ActionButton("##LiteExportKeys", TR("lite_show_private_keys"), ICON_MD_KEY, material::ActionTier::Secondary)) {
                            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);
                        }
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_show_keys"));

                        // 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);
                            }
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_copy"));
                            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) {
                                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_save_seed_file"));
                                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;
                            }
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_hide_wipe"));
                        }

                        // 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);
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_import_key"));
                        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 = TR("grpa_enter_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 (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_import_key_btn"));

                        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().textColored(TypeStyle::Overline, OnSurfaceMedium(), 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);
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_encrypt_pass"));
                            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;
                            }
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_encrypt"));
                        } 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);
                                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_unlock_pass"));
                                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");
                                }
                                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_unlock"));
                            } 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");
                                }
                                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_lock"));
                            }
                            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);
                            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_decrypt_pass"));
                            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 (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_remove_encrypt"));
                        }
                        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().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("lite_maintenance"));

                        const bool scanning = app->liteWallet()->scanInProgress();
                        ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
                        ImGui::BeginDisabled(scanning);
                        if (material::ActionButton("##literedl", TR("lite_redownload_blocks"), ICON_MD_CLOUD_DOWNLOAD, material::ActionTier::Secondary)) {
                            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));
                ndSingleBottom = ImGui::GetCursorScreenPos().y;   // lite = single card

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

                const float spMd = Layout::spacingMd();

                // Two-column layout when wide enough: Node / RPC / Security on the left, Daemon binary on
                // the right (fills the empty right side + shortens the card). One column when narrow.
                const bool  nsHasDaemon = app->supportsFullNodeLifecycleActions();
                const bool  nsTwoCol = nsHasDaemon && contentW > 760.0f * Layout::dpiScale();
                // The two columns become two SEPARATE glass panels: left [x0, x0+cardW],
                // right [x0+cardW+cardGap, x0+availWidth]. For the panels to keep a clean cardGap
                // between them, the content column must be cardW-2*pad and the right-column indent
                // (nsColW+nsColGap) must equal cardW+cardGap — so nsColGap = cardGap + 2*pad.
                const float nsColGap = Layout::cardGap() + 2.0f * pad;
                const float nsColW   = nsTwoCol ? ((availWidth - Layout::cardGap()) * 0.5f - 2.0f * pad) : contentW;
                ndTwoCol = nsTwoCol; ndColW = nsColW; ndColGap = nsColGap;   // hoist geometry for the two-panel draw
                const ImVec2 nsColTop = ImGui::GetCursorScreenPos();
                // Window-local anchor for the right column. We shift it with Indent() (not a one-shot
                // SetCursorScreenPos): ImGui resets the cursor X to the window's left indent on every
                // line-advance, so only an indent holds a column across multiple rows/widgets.
                const float nsColTopLocalX = ImGui::GetCursorPosX();
                const float nsColTopLocalY = ImGui::GetCursorPosY();
                {   // ---- left column (Node / RPC / Security) ----
                const float contentW = nsColW;
                const ImVec2 sectionOrigin(nsColTop.x, nsColTop.y);

                // -------------------- NODE / DATA --------------------
                Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("node"));
                ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                {
                    const std::string dirPath    = util::Platform::getDragonXDataDir();
                    const std::string walletPath  = dirPath + "wallet.dat";
                    const uint64_t    wallet_size = util::Platform::getFileSize(walletPath);
                    const std::string size_str    = (wallet_size > 0)
                        ? util::Platform::formatFileSize(wallet_size) : std::string(TR("settings_not_found"));
                    const float leftX  = ImGui::GetCursorPosX();
                    const float labelW = std::max(ImGui::CalcTextSize(TR("settings_data_dir")).x,
                                                  ImGui::CalcTextSize(TR("settings_wallet_size_label")).x)
                                         + Layout::spacingLg();
                    const ImU32 metaCol = OnSurfaceMedium();

                    // Row 1: Data directory — label left; clickable path + copy button RIGHT-aligned
                    // (mockup .kv space-between ledger look). The path middle-ellipsizes to fit.
                    ImGui::AlignTextToFramePadding();
                    ImGui::PushStyleColor(ImGuiCol_Text, metaCol);
                    ImGui::TextUnformatted(TR("settings_data_dir"));
                    ImGui::PopStyleColor();
                    ImFont* pathFont = ImGui::GetFont();
                    const float copyW = ImGui::GetFrameHeight();
                    const float pathAvailW = contentW - labelW - copyW - Layout::spacingSm() * 2.0f;
                    const std::string dirShown =
                        material::TruncateToWidth(dirPath, pathFont, pathFont->LegacySize, pathAvailW);
                    const float pathW = ImGui::CalcTextSize(dirShown.c_str()).x;
                    ImGui::SameLine(0, 0);
                    ImGui::SetCursorPosX(leftX + contentW - copyW - Layout::spacingSm() - pathW);
                    ImGui::AlignTextToFramePadding();
                    ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(Primary()), "%s", dirShown.c_str());
                    if (ImGui::IsItemHovered()) {
                        const ImVec2 tmn = ImGui::GetItemRectMin(), tmx = ImGui::GetItemRectMax();
                        dl->AddLine(ImVec2(tmn.x, tmx.y), ImVec2(tmx.x, tmx.y), Primary());
                        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                        material::Tooltip("%s\n%s", dirPath.c_str(), TR("tt_open_dir"));
                    }
                    if (ImGui::IsItemClicked()) util::Platform::openFolder(dirPath);
                    ImGui::SameLine(0, Layout::spacingSm());
                    {
                        material::IconButtonStyle cs;
                        cs.hoverBg = material::WithAlpha(OnSurface(), 30);
                        cs.tooltip = TR("copy");
                        const float ih = ImGui::GetFrameHeight();
                        if (material::IconButton("##copydir", ICON_MD_CONTENT_COPY, Type().iconSmall(), ImVec2(ih, ih), cs))
                            ImGui::SetClipboardText(dirPath.c_str());
                    }

                    // Row 2: Wallet size — label left, value RIGHT-aligned.
                    ImGui::AlignTextToFramePadding();
                    ImGui::PushStyleColor(ImGuiCol_Text, metaCol);
                    ImGui::TextUnformatted(TR("settings_wallet_size_label"));
                    ImGui::PopStyleColor();
                    {
                        const char* wv = (wallet_size > 0) ? size_str.c_str() : TR("settings_not_found");
                        const float wvW = ImGui::CalcTextSize(wv).x;
                        ImGui::SameLine(0, 0);
                        ImGui::SetCursorPosX(leftX + contentW - wvW);
                        ImGui::AlignTextToFramePadding();
                        if (wallet_size > 0) ImGui::TextUnformatted(wv);
                        else                 ImGui::TextDisabled("%s", wv);
                    }

                    // Large-wallet nudge: the BDB wallet.dat bloats with shielded-note witness data and
                    // never shrinks in place. Past a threshold, hint the user toward consolidating notes
                    // (Merge to Address) to curb further growth. Full-node only (lite has no wallet.dat here).
                    static constexpr uint64_t kWalletBloatWarnBytes = 500ull * 1024 * 1024;   // 500 MB
                    if (app->supportsFullNodeLifecycleActions() && wallet_size > kWalletBloatWarnBytes) {
                        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                        ImGui::PushStyleColor(ImGuiCol_Text, Warning());
                        ImGui::PushTextWrapPos(leftX + contentW);
                        ImGui::TextWrapped("%s", TR("wallet_size_warn"));
                        ImGui::PopTextWrapPos();
                        ImGui::PopStyleColor();
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_wallet_size_warn"));
                        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                        if (material::ActionButton("##walletconsolidate", TR("wallet_size_consolidate"),
                                                   ICON_MD_CALL_MERGE, material::ActionTier::Secondary))
                            ShieldDialog::showConsolidate();
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_merge"));
                    }

                    // Row 3: folder buttons (their own row so the path gets the full width).
                    ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
                    material::ButtonFlow ff(contentW);
                    ff.next(material::ActionButtonWidth(TR("settings_open_app_dir"), ICON_MD_FOLDER));
                    if (material::ActionButton("##openapp", TR("settings_open_app_dir"), ICON_MD_FOLDER, material::ActionTier::Secondary))
                        util::Platform::openFolder(util::Platform::getObsidianDragonDir());
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_app_dir"));
                    ff.next(material::ActionButtonWidth(TR("settings_open_data_dir"), ICON_MD_FOLDER_OPEN));
                    if (material::ActionButton("##opendata", TR("settings_open_data_dir"), ICON_MD_FOLDER_OPEN, material::ActionTier::Secondary))
                        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); material::Tooltip("%s", TR("tt_rpc_toggle")); }
                    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");
                    // Two rows, two column-aligned cells each: Host | Port, then Username | Password.
                    // Each input fills to its column's right edge so the two columns line up vertically.
                    const float colGap        = spMd;
                    const float colW          = std::floor((contentW - colGap) * 0.5f);
                    const float startX        = ImGui::GetCursorPosX();
                    const float leftColRight  = startX + colW;
                    const float rightColRight = startX + contentW;

                    // 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).
                    auto cell = [&](const char* label, const char* id, char* buf, size_t bufSz,
                                    float cellX, float cellRight, bool password) {
                        ImGui::SetCursorPosX(cellX);
                        ImGui::AlignTextToFramePadding();
                        ImGui::TextUnformatted(label);
                        ImGui::SameLine(0, Layout::spacingXs());
                        ImGui::SetNextItemWidth(std::max(60.0f, cellRight - ImGui::GetCursorPosX()));
                        ImGui::InputText(id, buf, bufSz,
                                         ImGuiInputTextFlags_ReadOnly |
                                         (password ? ImGuiInputTextFlags_Password : 0));
                    };

                    // Row 1: Host | Port
                    cell(hostLbl, "##RPCHost", s_settingsState.rpc_host, sizeof(s_settingsState.rpc_host), startX, leftColRight, false);
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_host"));
                    ImGui::SameLine();
                    cell(portLbl, "##RPCPort", s_settingsState.rpc_port, sizeof(s_settingsState.rpc_port), leftColRight + colGap, rightColRight, false);
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_port"));

                    // Row 2: Username | Password
                    cell(userLbl, "##RPCUser", s_settingsState.rpc_user, sizeof(s_settingsState.rpc_user), startX, leftColRight, false);
                    if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_user"));
                    ImGui::SameLine();
                    cell(passLbl, "##RPCPassword", s_settingsState.rpc_password, sizeof(s_settingsState.rpc_password), leftColRight + colGap, rightColRight, 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("%s", TR("rpc_plaintext_remote_warning"));
                        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);
                }   // ---- end left column ----
                const float nsLeftBottom = ImGui::GetCursorScreenPos().y;
                ndLeftBottom = nsLeftBottom;
                if (nsTwoCol) {
                    // Reset to the top, then indent so every line in the right column starts at the
                    // column X (the indent persists across line-advances; the explicit SetCursorPosX
                    // places the very first widget on the same line).
                    ImGui::SetCursorPosY(nsColTopLocalY);
                    ImGui::Indent(nsColW + nsColGap);
                    ImGui::SetCursorPosX(nsColTopLocalX + nsColW + nsColGap);
                }
                {   // ---- right column (Daemon binary) ----
                const float contentW = nsColW;

                // -------------------- DAEMON BINARY --------------------
                if (app->supportsFullNodeLifecycleActions()) {
                    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::spacingSm()));
                    // Heading row: "DAEMON BINARY" on the left, a compact colored status right-aligned
                    // on the same line (moved up out of the status box, and shortened).
                    {
                        const ImVec2 hp = ImGui::GetCursorScreenPos();
                        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary"));
                        if (bun.available) {
                            const bool sameSize = inst.exists && inst.size == bun.size;
                            const char* stTxt = !inst.exists ? TR("daemon_status_none")
                                              : sameSize     ? TR("daemon_status_ok")
                                                             : TR("daemon_status_diff");
                            const ImU32 stCol = (inst.exists && sameSize) ? Success() : Warning();
                            ImFont* ov = Type().overline();
                            const float stW = ov->CalcTextSizeA(ov->LegacySize, FLT_MAX, 0, stTxt).x;
                            dl->AddText(ov, ov->LegacySize, ImVec2(hp.x + contentW - stW, hp.y), stCol, stTxt);
                        }
                    }
                    ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

                    const float ddp = Layout::dpiScale();

                    // --- Status, grouped in a filled box (mockup .statusbox) ---
                    const float boxPad   = Layout::spacingMd();   // roomier inner padding (mockup ~9-11px)
                    const float boxLeftX = ImGui::GetCursorScreenPos().x;
                    ImGui::Dummy(ImVec2(0, boxPad));
                    ImGui::BeginGroup();
                    ImGui::Indent(boxPad);
                    const float dLeftX  = ImGui::GetCursorPosX();
                    const float dLabelW = std::max(ImGui::CalcTextSize(TR("daemon_installed")).x,
                                                   ImGui::CalcTextSize(TR("daemon_bundled")).x) + Layout::spacingLg();
                    auto dkv = [&](const char* label, const std::string& value, bool dim) {
                        ImGui::AlignTextToFramePadding();
                        ImGui::PushStyleColor(ImGuiCol_Text, OnSurfaceMedium());
                        ImGui::TextUnformatted(label);
                        ImGui::PopStyleColor();
                        ImGui::SameLine(0, 0);
                        // Right-align the value to the box edge (mockup .kv). If it's too long to fit
                        // (e.g. the installed version+size+date), fall back to left-packing after the label.
                        const float dvW = ImGui::CalcTextSize(value.c_str()).x;
                        ImGui::SetCursorPosX(std::max(dLeftX + dLabelW, dLeftX + (contentW - 2.0f * boxPad) - dvW));
                        ImGui::AlignTextToFramePadding();
                        if (dim) ImGui::TextDisabled("%s", value.c_str());
                        else     ImGui::TextUnformatted(value.c_str());
                    };
                    if (inst.exists) {
                        char ibuf[176];
                        std::snprintf(ibuf, sizeof(ibuf), "%s  \xC2\xB7  %s  \xC2\xB7  %s",
                                      inst.version.empty() ? TR("unknown") : inst.version.c_str(),
                                      util::Platform::formatFileSize(inst.size).c_str(),
                                      fmtDate(inst.modifiedEpoch).c_str());
                        dkv(TR("daemon_installed"), ibuf, false);
                    } else {
                        dkv(TR("daemon_installed"), TR("daemon_not_installed"), true);
                    }
                    if (bun.available) {
                        char bbuf[144];
                        std::snprintf(bbuf, sizeof(bbuf), "%s  \xC2\xB7  %s",
                                      bun.version.empty() ? TR("unknown") : bun.version.c_str(),
                                      util::Platform::formatFileSize(bun.size).c_str());
                        dkv(TR("daemon_bundled"), bbuf, false);
                    } else {
                        dkv(TR("daemon_bundled"), TR("daemon_none_bundled"), true);
                    }
                    ImGui::Unindent(boxPad);
                    ImGui::EndGroup();
                    {
                        const ImVec2 gmn = ImGui::GetItemRectMin(), gmx = ImGui::GetItemRectMax();
                        const ImVec2 bmn(boxLeftX, gmn.y - boxPad), bmx(boxLeftX + contentW, gmx.y + boxPad);
                        // Subtle lifted fill (mockup .statusbox #26262a on the #121317 card) + near-invisible border.
                        dl->AddRectFilled(bmn, bmx, material::WithAlpha(material::OnSurface(), 8), 8.0f * ddp);
                        dl->AddRect(bmn, bmx, material::WithAlpha(material::OnSurface(), 20), 8.0f * ddp, 0, 1.0f);
                    }
                    ImGui::Dummy(ImVec2(0, boxPad));

                    // Refresh the cached daemon info once an in-app install has completed.
                    if (ui::DaemonUpdateDialog::consumeInstalled())
                        s_settingsState.daemon_info_loaded = false;

                    // --- UPDATES ---
                    ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                    Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_updates_label"));
                    ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                    {
                        using AT = material::ActionTier;
                        material::ButtonFlow uf(contentW);
                        uf.next(material::ActionButtonWidth(TR("daemon_update_check"), ICON_MD_SYSTEM_UPDATE));
                        if (material::ActionButton("##dupd", TR("daemon_update_check"), ICON_MD_SYSTEM_UPDATE, AT::Primary))
                            ui::DaemonUpdateDialog::show(app, inst.exists ? inst.version : std::string());
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_update_check"));
                        uf.next(material::ActionButtonWidth(TR("refresh"), ICON_MD_REFRESH));
                        if (material::ActionButton("##drefresh", TR("refresh"), ICON_MD_REFRESH, AT::Secondary))
                            s_settingsState.daemon_info_loaded = false;
                        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_daemon_refresh"));
                        ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
                        uf.next(material::ActionButtonWidth(TR("daemon_install_bundled"), ICON_MD_DOWNLOAD));
                        if (material::ActionButton("##dinst", TR("daemon_install_bundled"), ICON_MD_DOWNLOAD, AT::Secondary))
                            s_settingsState.confirm_reinstall_daemon = true;
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_install_bundled"));
                        ImGui::EndDisabled();
                    }

                    // --- MAINTENANCE ---
                    ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                    Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_maintenance_label"));
                    ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
                    {
                        using AT = material::ActionTier;
                        material::ButtonFlow mf(contentW);
                        ImGui::BeginDisabled(!app->isConnected());
                        mf.next(material::ActionButtonWidth(TR("test_connection"), ICON_MD_LINK));
                        if (material::ActionButton("##dtest", TR("test_connection"), ICON_MD_LINK, AT::Secondary)) {
                            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(TR("settings_rpc_ok")); };
                                    } catch (const std::exception& e) {
                                        std::string err = e.what();
                                        return [err]() { Notifications::instance().error(std::string(TR("settings_rpc_error_prefix")) + err); };
                                    }
                                });
                            } else {
                                Notifications::instance().warning(TR("settings_not_connected"));
                            }
                        }
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
                        mf.next(material::ActionButtonWidth(TR("rescan"), ICON_MD_REFRESH));
                        if (material::ActionButton("##drescan", TR("rescan"), ICON_MD_REFRESH, AT::Secondary)) {
                            s_settingsState.confirm_rescan = true;
                            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::BeginDisabled(!app->isUsingEmbeddedDaemon());
                        mf.next(material::ActionButtonWidth(TR("repair_wallet"), ICON_MD_HEALING));
                        if (material::ActionButton("##drepair", TR("repair_wallet"), ICON_MD_HEALING, AT::Secondary))
                            s_settingsState.confirm_repair_wallet = true;
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_repair_wallet"));
                        ImGui::EndDisabled();
                    }

                    // --- Danger zone: Delete Blockchain, fenced off below a divider ---
                    ImGui::Dummy(ImVec2(0, Layout::spacingMd()));   // mockup .danger margin-top: 8px
                    {
                        const ImVec2 dvp = ImGui::GetCursorScreenPos();
                        // Neutral hairline (mockup .danger border-top #26262b) — not an alarming red rule.
                        dl->AddLine(dvp, ImVec2(dvp.x + contentW, dvp.y),
                                    material::WithAlpha(material::OnSurface(), 22), 1.0f);
                    }
                    ImGui::Dummy(ImVec2(0, Layout::spacingMd()));   // mockup .danger padding-top: 11px
                    {
                        using AT = material::ActionTier;
                        material::ButtonFlow df(contentW);
                        ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
                        df.next(material::ActionButtonWidth(TR("delete_blockchain"), ICON_MD_DELETE));
                        if (material::ActionButton("##ddelete", TR("delete_blockchain"), ICON_MD_DELETE, AT::Destructive))
                            s_settingsState.confirm_delete_blockchain = true;
                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_delete_blockchain"));
                        ImGui::EndDisabled();
                    }
                }
                }   // ---- end right column ----
                const float nsRightBottom = ImGui::GetCursorScreenPos().y;
                ndRightBottom = nsRightBottom; ndSingleBottom = nsRightBottom;
                if (nsTwoCol) ImGui::Unindent(nsColW + nsColGap);   // restore indent before the rest of the page
                ImGui::SetCursorScreenPos(ImVec2(nsColTop.x, nsTwoCol ? std::max(nsLeftBottom, nsRightBottom) : nsRightBottom));

                ImGui::PopFont();
            }

            // ---- Draw the glass card(s) behind the content, then merge the channels ----
            ImGui::Unindent(pad);
            dl->ChannelsSetCurrent(0);
            if (ndTwoCol) {
                const float ndEq = std::max(ndLeftBottom, ndRightBottom);   // equal-height cards (mockup grid stretch)
                material::DrawGlassPanel(dl, ImVec2(ndBaseX, ndTop),
                    ImVec2(ndBaseX + 2.0f * pad + ndColW, ndEq + bottomPad), glassSpec);
                material::DrawGlassPanel(dl, ImVec2(ndBaseX + ndColW + ndColGap, ndTop),
                    ImVec2(ndBaseX + availWidth, ndEq + bottomPad), glassSpec);
            } else {
                material::DrawGlassPanel(dl, ImVec2(ndBaseX, ndTop),
                    ImVec2(ndBaseX + availWidth, ndSingleBottom + bottomPad), glassSpec);
            }
            dl->ChannelsMerge();
            const float ndBot = ndTwoCol ? std::max(ndLeftBottom, ndRightBottom) : ndSingleBottom;
            ImGui::SetCursorScreenPos(ImVec2(ndBaseX, ndBot + bottomPad));
        }
    }

    // ====================================================================
    // EXPLORER & OPTIONS — full-width card
    // ====================================================================
    if (s_settingsState.current_tab == TAB_EXPLORER) {
        // Card 1 — URLS
        {
            material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
            float contentW = availWidth - pad * 2;
            Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("explorer_urls_hdr"));
            ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
            ImGui::PushFont(body2);

            // Transaction URL and Address URL — stacked rows, label left, input filling the card (mockup .row).
            const float urlLblW = std::max(ImGui::CalcTextSize(TR("transaction_url")).x,
                                           ImGui::CalcTextSize(TR("address_url")).x) + Layout::spacingMd();
            const float urlRowX  = ImGui::GetCursorPosX();
            const float urlInputW = contentW - urlLblW;

            ImGui::AlignTextToFramePadding();
            ImGui::TextUnformatted(TR("transaction_url"));
            ImGui::SameLine(urlRowX + urlLblW);
            ImGui::SetNextItemWidth(urlInputW);
            ImGui::InputText("##TxExplorer", s_settingsState.tx_explorer, sizeof(s_settingsState.tx_explorer));
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_tx_url"));

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

            ImGui::AlignTextToFramePadding();
            ImGui::TextUnformatted(TR("address_url"));
            ImGui::SameLine(urlRowX + urlLblW);
            ImGui::SetNextItemWidth(urlInputW);
            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, gap));

        // Card 2 — OPTIONS
        {
            material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
            float contentW = availWidth - pad * 2;
            Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet_options_hdr"));
            ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
            ImGui::PushFont(body2);

            // Checkboxes + Block Explorer button (button wraps to its own row when it won't fit).
            const float expRowRight = ImGui::GetCursorScreenPos().x + contentW;
            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"));
            const float expBtnW = ImGui::CalcTextSize(TR("block_explorer")).x
                                + ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg();
            if (ImGui::GetItemRectMax().x + expBtnW <= expRowRight)
                ImGui::SameLine(0, Layout::spacingLg());
            else
                ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
            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::PopFont();
        }
    }

    // ====================================================================
    // CHAT & CONTACTS — card (same controls as the Chat tab's settings notch)
    // ====================================================================
    if (s_settingsState.current_tab == TAB_CHAT) {
        // The shared control paints its own two cards (Appearance | Messaging) when drawCards=true.
        ImGui::PushFont(body2);
        RenderChatSettingsControls(app, availWidth, /*drawCards=*/true);
        ImGui::PopFont();
    }

    // ====================================================================
    // ABOUT — card
    // ====================================================================
    if (s_settingsState.current_tab == TAB_ABOUT) {
        material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
        const float contentW = availWidth - pad * 2;
        const float adp = Layout::dpiScale();
        const float baseX = ImGui::GetCursorScreenPos().x;

        // --- Header: small logo + title / tagline / tech line ---
        const ImVec2 logoTop = ImGui::GetCursorScreenPos();
        const float logoSz = 60.0f * adp;
        ImTextureID logoTex = app->getLogoTexture();
        const float logoAspect = (app->getLogoHeight() > 0)
            ? (float)app->getLogoWidth() / (float)app->getLogoHeight() : 1.0f;
        float logoAreaW = 0.0f;
        if (logoTex != 0) { logoAreaW = logoSz + Layout::spacingLg(); ImGui::Indent(logoAreaW); }

        ImGui::PushFont(sub1);
        ImGui::TextUnformatted(DRAGONX_APP_NAME);
        ImGui::PopFont();
        ImGui::SameLine(0, Layout::spacingSm());
        ImGui::PushFont(body2);
        snprintf(buf, sizeof(buf), "v%s", DRAGONX_VERSION);
        ImGui::TextColored(ImVec4(1, 1, 1, 0.5f), "%s", buf);
        ImGui::PopFont();

        ImGui::PushFont(body2);
        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(OnSurfaceMedium()));
        ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + (contentW - logoAreaW));
        ImGui::TextUnformatted(TR("settings_about_text"));
        ImGui::PopTextWrapPos();
        ImGui::PopStyleColor();
        ImGui::PopFont();

        ImGui::PushFont(capFont);
        snprintf(buf, sizeof(buf), "SDL3  \xC2\xB7  Dear ImGui %s  \xC2\xB7  GPL-3.0", IMGUI_VERSION);
        ImGui::TextColored(ImVec4(1, 1, 1, 0.4f), "%s", buf);
        ImGui::PopFont();

        if (logoTex != 0) ImGui::Unindent(logoAreaW);

        // Make the header at least as tall as the logo, then draw the logo centered in it.
        float headerH = ImGui::GetCursorScreenPos().y - logoTop.y;
        if (headerH < logoSz) { ImGui::Dummy(ImVec2(0, logoSz - headerH)); headerH = logoSz; }
        if (logoTex != 0) {
            float lw = logoSz, lh = logoSz;
            if (logoAspect >= 1.0f) lh = logoSz / logoAspect; else lw = logoSz * logoAspect;
            const float lx = logoTop.x + (logoSz - lw) * 0.5f;
            const float ly = logoTop.y + (headerH - lh) * 0.5f;
            dl->AddImage(logoTex, ImVec2(lx, ly), ImVec2(lx + lw, ly + lh));
        }

        // --- Divider ---
        ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
        {
            const ImVec2 dv = ImGui::GetCursorScreenPos();
            dl->AddLine(dv, ImVec2(dv.x + contentW, dv.y), ImGui::GetColorU32(material::Divider()), 1.0f);
        }
        ImGui::Dummy(ImVec2(0, Layout::spacingMd()));

        // --- Two columns: Credits (bullets) | License (paragraph + links) ---
        const float colGap = Layout::spacingLg();
        const float colW   = (contentW - colGap) * 0.5f;
        const float colTop = ImGui::GetCursorScreenPos().y;
        const float rx     = baseX + colW + colGap;

        // Left column — Credits
        ImGui::SetCursorScreenPos(ImVec2(baseX, colTop));
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("about_credits"));
        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
        {
            ImGui::PushFont(body2);
            static const char* kCredits[] = {
                "The Hush Developers",
                "The DragonX Developers",
                "ObsidianDragon Community",
                "Dear ImGui \xE2\x80\x94 Omar Cornut",
                "SDL3 \xE2\x80\x94 Sam Lantinga",
                "HushChat \xC2\xB7 librustzcash \xC2\xB7 libsodium",
            };
            for (size_t i = 0; i < std::size(kCredits); ++i) {
                const char* c = kCredits[i];
                const ImVec2 p = ImGui::GetCursorScreenPos();
                const float r = 2.5f * adp;
                dl->AddCircleFilled(ImVec2(p.x + r, p.y + ImGui::GetTextLineHeight() * 0.5f), r,
                                    material::WithAlpha(material::Primary(), 210));
                ImGui::SetCursorScreenPos(ImVec2(p.x + r * 2.0f + 8.0f * adp, p.y));
                ImGui::TextUnformatted(c);
                if (i < std::size(kCredits) - 1) ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
            }
            ImGui::PopFont();
        }
        const float leftBottom = ImGui::GetCursorScreenPos().y;

        // Right column — License + links
        ImGui::SetCursorScreenPos(ImVec2(rx, colTop));
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("about_license"));
        {
            ImGui::SetCursorScreenPos(ImVec2(rx, ImGui::GetCursorScreenPos().y + Layout::spacingSm()));
            ImGui::PushFont(capFont);
            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 0.6f));
            ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + colW);
            ImGui::TextUnformatted(TR("about_license_text"));
            ImGui::PopTextWrapPos();
            ImGui::PopStyleColor();
            ImGui::PopFont();
        }
        ImGui::Dummy(ImVec2(0, Layout::spacingLg()));
        {
            using AT = material::ActionTier;
            ImGui::SetCursorScreenPos(ImVec2(rx, ImGui::GetCursorScreenPos().y));
            material::ButtonFlow lf(colW);
            lf.next(material::ActionButtonWidth(TR("website"), ICON_MD_PUBLIC));
            if (material::ActionButton("##aboutweb", TR("website"), ICON_MD_PUBLIC, AT::Secondary))
                util::Platform::openUrl("https://dragonx.is");
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_website"));
            lf.next(material::ActionButtonWidth(TR("about_source"), ICON_MD_CODE));
            if (material::ActionButton("##aboutsrc", TR("about_source"), ICON_MD_CODE, AT::Secondary))
                util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon");
            lf.next(material::ActionButtonWidth(TR("report_bug"), ICON_MD_BUG_REPORT));
            if (material::ActionButton("##aboutbug", TR("report_bug"), ICON_MD_BUG_REPORT, AT::Secondary))
                util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon/issues");
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_report_bug"));
            lf.next(material::ActionButtonWidth(TR("faq"), ICON_MD_QUESTION_MARK));
            if (material::ActionButton("##aboutfaq", TR("faq"), ICON_MD_QUESTION_MARK, AT::Secondary))
                app->showFaqDialog();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("faq_open_tooltip"));
        }
        const float rightBottom = ImGui::GetCursorScreenPos().y;

        // Reconcile the two columns, then a card-wide settings-actions row.
        ImGui::SetCursorScreenPos(ImVec2(baseX, std::max(leftBottom, rightBottom)));
        ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
        {
            using AT = material::ActionTier;
            material::ButtonFlow af(contentW);
            af.next(material::ActionButtonWidth(TR("save_settings"), ICON_MD_SAVE));
            if (material::ActionButton("##aboutsave", TR("save_settings"), ICON_MD_SAVE, AT::Secondary)) {
                saveSettingsPageState(app->settings());
                Notifications::instance().success(TR("settings_saved"));
            }
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_save_settings"));
            af.next(material::ActionButtonWidth(TR("reset_to_defaults"), ICON_MD_RESTORE));
            if (material::ActionButton("##aboutreset", TR("reset_to_defaults"), ICON_MD_RESTORE, AT::Tertiary)) {
                if (app->settings()) { loadSettingsPageState(app->settings()); Notifications::instance().info(TR("settings_reloaded")); }
            }
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_reset_settings"));
        }
    }

    if (s_settingsState.current_tab == TAB_NODE)
        ImGui::Dummy(ImVec2(0, gap));

    // ====================================================================
    // MINING POOL HOSTING — run this node's built-in RandomX stratum server so other miners can point
    // at this machine. It's a node feature, so it belongs here (not the Wallet tab). Full-node only, and
    // only on a daemon new enough to implement -stratum (v1.3.0+, version encoded
    // major*1e6+minor*1e4+rev*100+build). Takes effect on the next daemon start/restart; a blank allow-IP
    // keeps it loopback-only (safe), a subnet opens it to that LAN.
    // ====================================================================
    if (app->supportsFullNodeLifecycleActions() && s_settingsState.current_tab == TAB_NODE &&
        app->state().daemon_version >= 1030000) {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("stratum_host_section"));
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
        material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
        if (ImGui::Checkbox(TR("stratum_host"), &s_settingsState.stratum_host))
            saveSettingsPageState(app->settings());
        if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_stratum_host"));
        if (s_settingsState.stratum_host) {
            ImGui::TextDisabled("    %s", TR("stratum_host_hint"));
            ImGui::SetNextItemWidth(220.0f * Layout::dpiScale());
            if (ImGui::InputTextWithHint("##stratumallowip", TR("stratum_allowip_hint"),
                    s_settingsState.stratum_allowip, sizeof(s_settingsState.stratum_allowip)))
                saveSettingsPageState(app->settings());
            if (s_settingsState.stratum_allowip[0] != '\0')
                ImGui::TextColored(ImVec4(0.95f, 0.75f, 0.25f, 1.0f), "    %s",
                                   TR("stratum_expose_warn"));
        }
    }

    // ====================================================================
    // DEBUG OPTIONS — collapsible card (full-node only: holds the screenshot sweep + the dragonxd
    // daemon debug= categories written to DRAGONX.conf; lite has no daemon). Shown on the Node tab.
    // ====================================================================
    if (app->supportsFullNodeLifecycleActions() && s_settingsState.current_tab == TAB_NODE) {
        // 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();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_screenshot_sweep"));
            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();
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_screenshot_sweep_full"));
            ImGui::SameLine();
            if (TactileButton(TR("screenshot_open_dir"), ImVec2(0, 0), S.resolveFont("button")))
                util::Platform::openFolder(app->screenshotDir());
            if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_screenshot_open_dir"));
            if (chat::hushChatFeatureEnabledAtBuild()) {
                // Populate the Chat tab with demo conversations so the sweep captures its real UI.
                ImGui::SameLine();
                if (TactileButton(TR("grpa_seed_demo_chat"), ImVec2(0, 0), S.resolveFont("button")))
                    app->seedChatDemoData();
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_demo_chat"));
            }
            // Restrict either sweep to just the active theme instead of cycling every skin.
            ImGui::SameLine();
            {
                bool only = app->sweepCurrentThemeOnly();
                if (ImGui::Checkbox(TR("sweep_current_theme_only"), &only))
                    app->setSweepCurrentThemeOnly(only);
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_sweep_current_theme_only"));
            }
            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"
            };
            const char* debugTips[] = {
                TR("grpa_dbg_addrman"),
                TR("grpa_dbg_alert"),
                TR("grpa_dbg_bench"),
                TR("grpa_dbg_coindb"),
                TR("grpa_dbg_db"),
                TR("grpa_dbg_estimatefee"),
                TR("grpa_dbg_http"),
                TR("grpa_dbg_libevent"),
                TR("grpa_dbg_lock"),
                TR("grpa_dbg_mempool"),
                TR("grpa_dbg_net"),
                TR("grpa_dbg_paymentdisclosure"),
                TR("grpa_dbg_pow"),
                TR("grpa_dbg_proxy"),
                TR("grpa_dbg_prune"),
                TR("grpa_dbg_rand"),
                TR("grpa_dbg_reindex"),
                TR("grpa_dbg_rpc"),
                TR("grpa_dbg_selectcoins"),
                TR("grpa_dbg_tor"),
                TR("grpa_dbg_zmq"),
                TR("grpa_dbg_zrpc")
            };
            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(TR("settings_ztx_cleared"));
                } else {
                    Notifications::instance().info(TR("settings_ztx_not_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 * dp);
                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 * dp))) {
                s_settingsState.confirm_rescan = false;
            }
            ImGui::SameLine();
            ImGui::BeginDisabled(detecting);
            if (material::TactileButton(TrId("rescan", "rescan_confirm").c_str(), ImVec2(btnW, 40 * dp))) {
                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
