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

#include "balance_tab.h"
#include "balance_address_list.h"
#include "balance_tab_helpers.h"
#include "balance_recent_tx.h"
#include "key_export_dialog.h"
#include "qr_popup_dialog.h"
#include "address_label_dialog.h"
#include "address_transfer_dialog.h"
#include "send_tab.h"
#include "../../app.h"
#include "../../config/settings.h"
#include "../../config/version.h"
#include "../../util/i18n.h"
#include "../theme.h"
#include "../layout.h"
#include "../schema/ui_schema.h"
#include "../material/type.h"
#include "../material/draw_helpers.h"
#include "../effects/imgui_acrylic.h"
#include "../sidebar.h"
#include "../notifications.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include <toml++/toml.hpp>
#include <algorithm>
#include <cstring>
#include <ctime>
#include <cmath>
#include "../../util/logger.h"

namespace dragonx {
namespace ui {

// Animated balance state — lerps smoothly toward target
static double s_dispTotal       = 0.0;
static double s_dispShielded    = 0.0;
static double s_dispTransparent = 0.0;
static double s_dispUnconfirmed = 0.0;

// Forward declarations for all layout functions
static void RenderBalanceClassic(App* app);
static void RenderBalanceDonut(App* app);
static void RenderBalanceConsolidated(App* app);
static void RenderBalanceDashboard(App* app);
static void RenderBalanceVerticalStack(App* app);
static void RenderBalanceVertical2x2(App* app);
static void RenderBalanceShield(App* app);
static void RenderBalanceTimeline(App* app);
static void RenderBalanceTwoRow(App* app);
static void RenderBalanceMinimal(App* app);

// ============================================================================
// Layout config — parsed from ui.toml [tabs.balance.layouts]
// ============================================================================

// Legacy int→string ID mapping for old settings.json migration
static const char* s_legacyLayoutIds[] = {
    "classic", "donut", "consolidated", "dashboard",
    "vertical-stack", "vertical-2x2", "shield", "timeline", "two-row", "minimal"
};
static constexpr int s_legacyLayoutCount = 10;

static std::vector<BalanceLayoutEntry> s_balanceLayouts;
static std::string s_defaultLayoutId = "classic";
static bool s_layoutConfigLoaded = false;
static bool s_generating_z_address = false;

static void LoadBalanceLayoutConfig()
{
    s_balanceLayouts.clear();

    const void* elem = schema::UI().findElement("tabs.balance", "layouts");
    if (elem) {
        const auto& t = *static_cast<const toml::table*>(elem);

        if (auto selected = t["selected"].value<std::string>())
            s_defaultLayoutId = *selected;
        else if (auto def = t["default"].value<std::string>())
            s_defaultLayoutId = *def;

        if (auto* options = t["options"].as_array()) {
            for (auto& item : *options) {
                auto* opt = item.as_table();
                if (!opt) continue;
                auto id = (*opt)["id"].value<std::string>();
                auto name = (*opt)["name"].value<std::string>();
                if (!id || !name) continue;

                BalanceLayoutEntry entry;
                entry.id = *id;
                entry.name = *name;
                entry.enabled = (*opt)["enabled"].value_or(true);
                s_balanceLayouts.push_back(std::move(entry));
            }
        }
    }

    // Fallback if ui.toml had no layouts defined
    if (s_balanceLayouts.empty()) {
        for (int i = 0; i < s_legacyLayoutCount; i++) {
            BalanceLayoutEntry entry;
            entry.id = s_legacyLayoutIds[i];
            // Capitalize first letter for display name
            entry.name = entry.id;
            if (!entry.name.empty())
                entry.name[0] = (char)toupper((unsigned char)entry.name[0]);
            s_balanceLayouts.push_back(std::move(entry));
        }
    }
    s_layoutConfigLoaded = true;
}

const std::vector<BalanceLayoutEntry>& GetBalanceLayouts()
{
    if (!s_layoutConfigLoaded) LoadBalanceLayoutConfig();
    return s_balanceLayouts;
}

const std::string& GetDefaultBalanceLayout()
{
    if (!s_layoutConfigLoaded) LoadBalanceLayoutConfig();
    return s_defaultLayoutId;
}

void RefreshBalanceLayoutConfig()
{
    s_layoutConfigLoaded = false;
}

std::string MigrateBalanceLayoutIndex(int index)
{
    if (index >= 0 && index < s_legacyLayoutCount)
        return s_legacyLayoutIds[index];
    return "classic";
}

// Layout ID → render function dispatch
using LayoutRenderFn = void(*)(App*);
struct LayoutDispatchEntry { const char* id; LayoutRenderFn fn; };
static const LayoutDispatchEntry s_layoutDispatch[] = {
    { "classic",        RenderBalanceClassic },
    { "donut",          RenderBalanceDonut },
    { "consolidated",   RenderBalanceConsolidated },
    { "dashboard",      RenderBalanceDashboard },
    { "vertical-stack", RenderBalanceVerticalStack },
    { "vertical-2x2",   RenderBalanceVertical2x2 },
    { "shield",         RenderBalanceShield },
    { "timeline",       RenderBalanceTimeline },
    { "two-row",        RenderBalanceTwoRow },
    { "minimal",        RenderBalanceMinimal },
};

void RenderBalanceTab(App* app)
{
    std::string layoutId = GetDefaultBalanceLayout();
    if (app->settings()) {
        std::string saved = app->settings()->getBalanceLayout();
        if (!saved.empty()) layoutId = saved;
    }

    // Left/Right arrows: cycle through enabled balance layouts
    // (skip when Ctrl is held — Ctrl+Arrow cycles themes instead)
    if (app->settings() && !ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl) {
        bool cycleUp   = ImGui::IsKeyPressed(ImGuiKey_LeftArrow);
        bool cycleDown = ImGui::IsKeyPressed(ImGuiKey_RightArrow);
        if (cycleUp || cycleDown) {
            const auto& layouts = GetBalanceLayouts();
            // Build list of enabled layout IDs
            std::vector<std::string> enabled;
            for (const auto& l : layouts)
                if (l.enabled) enabled.push_back(l.id);
            if (!enabled.empty()) {
                int cur = 0;
                for (int i = 0; i < (int)enabled.size(); i++) {
                    if (enabled[i] == layoutId) { cur = i; break; }
                }
                if (cycleUp)
                    cur = (cur - 1 + (int)enabled.size()) % (int)enabled.size();
                else
                    cur = (cur + 1) % (int)enabled.size();
                layoutId = enabled[cur];
                app->settings()->setBalanceLayout(layoutId);

                // Show toast with layout name
                const auto& allLayouts = GetBalanceLayouts();
                std::string displayName = layoutId;
                for (const auto& l : allLayouts) {
                    if (l.id == layoutId) { displayName = l.name; break; }
                }
                Notifications::instance().info("Layout: " + displayName);
            }
        }
    }

    // Dispatch by string ID
    for (const auto& entry : s_layoutDispatch) {
        if (layoutId == entry.id) {
            entry.fn(app);
            return;
        }
    }
    // Fallback to Classic
    RenderBalanceClassic(app);
}

// ============================================================================
// Layout 0: Classic (original 3-card layout)
// ============================================================================
static void RenderBalanceClassic(App* app)
{
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto addrBtn    = S.button("tabs.balance", "address-button");
    const auto actionBtn  = S.button("tabs.balance", "action-button");
    const auto searchIn   = S.input("tabs.balance", "search-input");
    const auto addrTable  = S.table("tabs.balance", "address-table");
    const auto syncBar    = S.drawElement("tabs.balance", "sync-bar");

    // Read layout properties from schema
    const float kBalanceLerpSpeed    = S.drawElement("tabs.balance", "balance-lerp-speed").sizeOr(8.0f);
    const float kHeroPadTop          = S.drawElement("tabs.balance", "hero-pad-top").sizeOr(12.0f);
    const float kRecentTxRowHeight   = S.drawElement("tabs.balance", "recent-tx-row-height").sizeOr(22.0f);
    const float kButtonRowRightMargin = S.drawElement("tabs.balance", "button-row-right-margin").sizeOr(16.0f);

    const auto& state = app->state();

    // Responsive scale factors (recomputed every frame)
    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    const float hs = Layout::hScale(contentAvail.x);
    const float vs = Layout::vScale(contentAvail.y);
    const auto tier = Layout::currentTier(contentAvail.x, contentAvail.y);
    const float glassRound = Layout::glassRounding();
    const float cGap       = Layout::cardGap();
    const float dp         = Layout::dpiScale();

    // Responsive constants (scale with window size)
    const float kSparklineHeight    = std::max(S.drawElement("tabs.balance", "sparkline-min-height").size, S.drawElement("tabs.balance", "sparkline-height").size * vs);
    const float kMinButtonsPosition = std::max(S.drawElement("tabs.balance", "min-buttons-position").size, S.drawElement("tabs.balance", "buttons-position").size * hs);

    // Dynamic recent tx count — fit as many as space allows
    const float scaledRowH = std::max(S.drawElement("tabs.balance", "recent-tx-row-min-height").size, kRecentTxRowHeight * vs);
    // At minimum size ~601px avail, reserve ~300px for hero+cards+addr header,
    // rest for address list + recent txs. Show 3-5 rows depending on space.
    const int kRecentTxCount = std::clamp(
        (int)((contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f)) / scaledRowH), 2, 5);

    // Lerp displayed balances toward actual values
    {
        float dt = ImGui::GetIO().DeltaTime;
        float speed = (app->settings() && app->settings()->getReduceMotion()) ? 999.0f : kBalanceLerpSpeed;
        auto lerp = [](double& disp, double target, float dt, float spd) {
            double diff = target - disp;
            if (std::abs(diff) < 1e-9) { disp = target; return; }
            disp += diff * (double)(dt * spd);
            // Snap when very close
            if (std::abs(target - disp) < 1e-9) disp = target;
        };
        lerp(s_dispTotal,       app->getTotalBalance(),       dt, speed);
        lerp(s_dispShielded,    app->getShieldedBalance(),    dt, speed);
        lerp(s_dispTransparent, app->getTransparentBalance(), dt, speed);
        lerp(s_dispUnconfirmed, state.unconfirmed_balance,    dt, speed);
    }

    // ================================================================
    // Card row — Total Balance | Shielded | Transparent | Market
    // ================================================================
    {
        float topMargin = S.drawElement("tabs.balance.classic", "top-margin").size;
        if (topMargin > 0.0f)
            ImGui::Dummy(ImVec2(0, topMargin));
        else if (topMargin < 0.0f) {
            // auto: use hero-pad-top scaled by vertical factor
            float autoPad = kHeroPadTop * vs;
            if (autoPad > 0.0f)
                ImGui::Dummy(ImVec2(0, autoPad));
        }
        // topMargin == 0 → no spacing at all

        const float cardGap = cGap;
        float availWidth = ImGui::GetContentRegionAvail().x;

        // Responsive card columns: 4 normally, 2 in compact, 1 if very narrow
        int numCols = (int)S.drawElement("tabs.balance.classic", "card-num-cols").sizeOr(4.0f);
        if (tier == Layout::LayoutTier::Compact) {
            if (availWidth < S.drawElement("tabs.balance.classic", "card-narrow-width").sizeOr(400.0f) * Layout::dpiScale())
                numCols = (int)S.drawElement("tabs.balance.classic", "card-narrow-cols").sizeOr(1.0f);
            else
                numCols = (int)S.drawElement("tabs.balance.classic", "card-compact-cols").sizeOr(2.0f);
        }
        float cardWidth  = (availWidth - (float)(numCols - 1) * cardGap) / (float)numCols;

        ImDrawList* dl = ImGui::GetWindowDrawList();
        ImVec2 origin  = ImGui::GetCursorScreenPos();
        GlassPanelSpec cardSpec;
        cardSpec.rounding = glassRound;

        char buf[64];
        ImU32 greenCol = Success();
        ImU32 goldCol  = Warning();
        ImU32 amberCol = Warning();

        ImFont* ovFont  = Type().overline();
        ImFont* sub1    = Type().subtitle1();
        ImFont* capFont = Type().caption();

        float classicPadOverride = S.drawElement("tabs.balance.classic", "card-padding").size;
        float cardPadLg = (classicPadOverride >= 0.0f) ? classicPadOverride : Layout::spacingLg();

        // Card height: must fit the Market card's content (overline + price + 24h)
        const float ovGap = S.drawElement("tabs.balance", "overline-value-gap").sizeOr(6.0f);
        const float valGap = S.drawElement("tabs.balance", "value-caption-gap").sizeOr(4.0f);
        const float tickGap = S.drawElement("tabs.balance.classic", "ticker-gap").sizeOr(4.0f);
        float marketContentH = cardPadLg
                             + ovFont->LegacySize + ovGap
                             + sub1->LegacySize + 2.0f * dp
                             + capFont->LegacySize
                             + cardPadLg;
        float classicCardH = S.drawElement("tabs.balance.classic", "card-height").size;
        float cardH;
        if (classicCardH >= 0.0f) {
            // TOML override scaled by dp so it grows with DPI + user font scale
            cardH = std::max(classicCardH * dp, marketContentH);
        } else {
            float minH = S.drawElement("tabs.balance.classic", "card-min-height").sizeOr(70.0f) * dp;
            cardH = std::max(StatCardHeight(vs, minH), marketContentH);
        }

        // Helper: draw accent stripe on left edge, clipped to card rounded corners.
        // We draw a full-size rounded rect (left corners only) and clip it to the
        // stripe width so the shape itself follows the card rounding.
        const float accentW      = S.drawElement("tabs.balance", "accent-width").sizeOr(4.0f);
        auto drawAccent = [&](const ImVec2& cMin, const ImVec2& cMax, ImU32 col) {
            dl->PushClipRect(cMin, ImVec2(cMin.x + accentW, cMax.y), true);
            dl->AddRectFilled(cMin, cMax, col, cardSpec.rounding,
                              ImDrawFlags_RoundCornersLeft);
            dl->PopClipRect();
        };

        // Helper: compute card position given card index (0-3)
        auto cardPos = [&](int idx) -> ImVec2 {
            int col = idx % numCols;
            int row = idx / numCols;
            return ImVec2(origin.x + col * (cardWidth + cardGap),
                          origin.y + row * (cardH + cardGap));
        };

        // ---- Total Balance card ----
        {
            ImVec2 cMin = cardPos(0);
            ImVec2 cMax(cMin.x + cardWidth, cMin.y + cardH);
            DrawGlassPanel(dl, cMin, cMax, cardSpec);
            drawAccent(cMin, cMax, S.resolveColor("var(--accent-total)", OnSurface()));

            float cx = cMin.x + cardPadLg;
            float cy = cMin.y + cardPadLg;

            // Coin logo (small, top-right corner)
            ImTextureID logoTex = app->getCoinLogoTexture();
            if (logoTex != 0) {
                float logoSz = ovFont->LegacySize + sub1->LegacySize + 4.0f * dp;
                float logoX = cMax.x - cardPadLg - logoSz;
                float logoY = cMin.y + cardPadLg;
                dl->AddImage(logoTex,
                    ImVec2(logoX, logoY),
                    ImVec2(logoX + logoSz, logoY + logoSz),
                    ImVec2(0, 0), ImVec2(1, 1),
                    IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance.classic", "logo-opacity").sizeOr(180.0f)));
            }

            dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy),
                        OnSurfaceMedium(), "TOTAL BALANCE");
            cy += ovFont->LegacySize + ovGap;

            snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
            dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), OnSurface(), buf);
            ImVec2 balSz = sub1->CalcTextSizeA(sub1->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(cx + balSz.x + tickGap,
                               cy + sub1->LegacySize - capFont->LegacySize),
                        OnSurfaceMedium(), DRAGONX_TICKER);
            cy += sub1->LegacySize + valGap;

            // USD value
            {
                double usd_value = state.getBalanceUSD();
                if (usd_value > 0.0)
                    snprintf(buf, sizeof(buf), "$%.2f USD", usd_value);
                else
                    snprintf(buf, sizeof(buf), "$-.-- USD");
                dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy),
                            OnSurfaceDisabled(), buf);
            }
            cy += capFont->LegacySize + 2 * dp;

            // Sync progress or mining indicator (whichever fits)
            if (state.sync.syncing && state.sync.headers > 0) {
                float pct = static_cast<float>(state.sync.verification_progress) * 100.0f;
                snprintf(buf, sizeof(buf), "Syncing %.1f%%", pct);
                dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy),
                            Warning(), buf);

                // Thin sync bar at card bottom — clipped to card rounded corners
                float barH = (syncBar.height >= 0) ? syncBar.height : 3.0f;
                float prog = static_cast<float>(state.sync.verification_progress);
                if (prog > 1.0f) prog = 1.0f;
                float barTop = cMax.y - barH;
                // Clip to the bottom strip so the full-card rounded rect
                // curves exactly match the card's own rounded corners
                dl->PushClipRect(ImVec2(cMin.x, barTop), cMax, true);
                // Background track
                dl->AddRectFilled(cMin, cMax,
                    IM_COL32(255, 255, 255, 15), cardSpec.rounding);
                // Progress fill — additional horizontal clip
                float progRight = cMin.x + (cMax.x - cMin.x) * prog;
                dl->PushClipRect(ImVec2(cMin.x, barTop), ImVec2(progRight, cMax.y), true);
                dl->AddRectFilled(cMin, cMax,
                    WithAlpha(Warning(), 200), cardSpec.rounding);
                dl->PopClipRect();
                dl->PopClipRect();
            } else if (state.mining.generate) {
                float pulse = schema::UI().drawElement("animations", "pulse-base-normal").size
                    + schema::UI().drawElement("animations", "pulse-amp-normal").size
                    * (float)std::sin((double)ImGui::GetTime()
                    * schema::UI().drawElement("animations", "pulse-speed-normal").size);
                ImU32 mineCol = WithAlpha(Success(), (int)(255.0f * pulse));
                dl->AddCircleFilled(ImVec2(cx + 4 * dp, cy + capFont->LegacySize * 0.5f),
                    S.drawElement("tabs.balance.classic", "mining-dot-radius").sizeOr(3.0f), mineCol);
                double hr = state.mining.localHashrate;
                if (hr >= 1000.0)
                    snprintf(buf, sizeof(buf), "  Mining %.1f KH/s", hr / 1000.0);
                else
                    snprintf(buf, sizeof(buf), "  Mining %.0f H/s", hr);
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(cx + 12 * dp, cy),
                            WithAlpha(Success(), 200), buf);
            }

            // Hover glow
            if (material::IsRectHovered(cMin, cMax)) {
                dl->AddRect(cMin, cMax, IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance", "hover-glow-alpha").sizeOr(40.0f)),
                            cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f));
            }
        }

        // ---- Shielded card ----
        {
            ImVec2 cMin = cardPos(1);
            ImVec2 cMax(cMin.x + cardWidth, cMin.y + cardH);
            DrawGlassPanel(dl, cMin, cMax, cardSpec);
            drawAccent(cMin, cMax, WithAlpha(S.resolveColor("var(--accent-shielded)", Success()), 200));

            float cx = cMin.x + cardPadLg;
            float cy = cMin.y + cardPadLg;

            dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy),
                        OnSurfaceMedium(), TR("shielded"));
            cy += ovFont->LegacySize + ovGap;

            snprintf(buf, sizeof(buf), "%.8f", s_dispShielded);
            dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), greenCol, buf);
            ImVec2 balSz = sub1->CalcTextSizeA(sub1->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(cx + balSz.x + tickGap,
                               cy + sub1->LegacySize - capFont->LegacySize),
                        OnSurfaceMedium(), DRAGONX_TICKER);
            cy += sub1->LegacySize + valGap;

            // Privacy ratio + address count
            {
                float privPct = (s_dispTotal > 1e-9)
                    ? (float)(s_dispShielded / s_dispTotal * 100.0) : 0.0f;
                snprintf(buf, sizeof(buf), "%.0f%% of total  ·  %d Z-addr",
                         privPct, (int)state.z_addresses.size());
                dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy),
                            WithAlpha(Success(), 160), buf);
            }

            // Unconfirmed badge (top-right corner)
            if (state.unconfirmed_balance > 0.0) {
                snprintf(buf, sizeof(buf), "+%.4f", state.unconfirmed_balance);
                ImVec2 ts = capFont->CalcTextSizeA(
                    capFont->LegacySize, 10000, 0, buf);
                float bp = S.drawElement("tabs.balance.classic", "unconfirmed-badge-padding").sizeOr(4.0f);
                float br = S.drawElement("tabs.balance.classic", "unconfirmed-badge-rounding").sizeOr(4.0f);
                ImVec2 bMin(cMax.x - ts.x - bp * 3,
                            cMin.y + cardPadLg);
                ImVec2 bMax(cMax.x - bp, bMin.y + ts.y + bp);
                dl->AddRectFilled(bMin, bMax,
                                  WithAlpha(Warning(), 40), br);
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(bMin.x + bp, bMin.y + bp * 0.5f),
                            amberCol, buf);
            }

            // Hover glow + click to Receive
            if (material::IsRectHovered(cMin, cMax)) {
                dl->AddRect(cMin, cMax, IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance", "hover-glow-alpha").sizeOr(40.0f)),
                            cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f));
                ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                if (ImGui::IsMouseClicked(0))
                    app->setCurrentPage(NavPage::Receive);
            }
        }

        // ---- Transparent card ----
        {
            ImVec2 cMin = cardPos(2);
            ImVec2 cMax(cMin.x + cardWidth, cMin.y + cardH);
            DrawGlassPanel(dl, cMin, cMax, cardSpec);
            drawAccent(cMin, cMax, WithAlpha(S.resolveColor("var(--accent-transparent)", Warning()), 200));

            float cx = cMin.x + cardPadLg;
            float cy = cMin.y + cardPadLg;

            dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy),
                        OnSurfaceMedium(), TR("transparent"));
            cy += ovFont->LegacySize + ovGap;

            snprintf(buf, sizeof(buf), "%.8f", s_dispTransparent);
            dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), goldCol, buf);
            ImVec2 balSz = sub1->CalcTextSizeA(sub1->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(cx + balSz.x + tickGap,
                               cy + sub1->LegacySize - capFont->LegacySize),
                        OnSurfaceMedium(), DRAGONX_TICKER);
            cy += sub1->LegacySize + valGap;

            snprintf(buf, sizeof(buf), "%d T-addresses",
                     (int)state.t_addresses.size());
            dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy),
                        OnSurfaceDisabled(), buf);

            // Hover glow + click to Receive
            if (material::IsRectHovered(cMin, cMax)) {
                dl->AddRect(cMin, cMax, IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance", "hover-glow-alpha").sizeOr(40.0f)),
                            cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f));
                ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                if (ImGui::IsMouseClicked(0))
                    app->setCurrentPage(NavPage::Receive);
            }
        }

        // ---- Market card ----
        {
            ImVec2 cMin = cardPos(3);
            ImVec2 cMax(cMin.x + cardWidth, cMin.y + cardH);
            DrawGlassPanel(dl, cMin, cMax, cardSpec);
            drawAccent(cMin, cMax, S.resolveColor("var(--accent-action)", Primary()));

            float cx = cMin.x + cardPadLg;
            float cy = cMin.y + cardPadLg;

            // Price string (compute early to measure text width)
            const auto& market = state.market;
            if (market.price_usd > 0) {
                if (market.price_usd >= 0.01)
                    snprintf(buf, sizeof(buf), "$%.4f", market.price_usd);
                else if (market.price_usd >= 0.0001)
                    snprintf(buf, sizeof(buf), "$%.6f", market.price_usd);
                else
                    snprintf(buf, sizeof(buf), "$%.8f", market.price_usd);
            } else {
                snprintf(buf, sizeof(buf), "$--.--");
            }
            ImVec2 pSz = sub1->CalcTextSizeA(sub1->LegacySize, 10000, 0, buf);
            ImVec2 usdSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, "USD");

            // Measure widest text line to determine sparkline left edge
            float textW = std::max(pSz.x + tickGap + usdSz.x,
                                   ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, "MARKET").x);
            float sparkGap = S.drawElement("tabs.balance.classic", "sparkline-gap").sizeOr(12.0f);
            float sparkLeft = cx + textW + sparkGap;
            float sparkRight = cMax.x - cardPadLg;

            // Left side: label + price + 24h change
            dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy),
                        OnSurfaceMedium(), "MARKET");
            cy += ovFont->LegacySize + ovGap;

            dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy),
                        OnSurface(), buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(cx + pSz.x + tickGap,
                               cy + sub1->LegacySize - capFont->LegacySize),
                        OnSurfaceMedium(), "USD");
            cy += sub1->LegacySize + valGap;

            // 24h change
            if (market.price_usd > 0) {
                bool pos = market.change_24h >= 0;
                ImU32 chgCol = pos ? Success()
                                   : Error();
                snprintf(buf, sizeof(buf), "%s%.1f%% 24h",
                         pos ? "+" : "", market.change_24h);
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(cx, cy), chgCol, buf);
            }

            // Right side: sparkline fills remaining card space
            if (market.price_history.size() >= 2 && sparkLeft < sparkRight) {
                float spTop = cMin.y + cardPadLg;
                float spBot = cMax.y - cardPadLg;
                ImVec2 spMin(sparkLeft, spTop);
                ImVec2 spMax(sparkRight, spBot);
                ImU32 lineCol = market.change_24h >= 0
                    ? WithAlpha(Success(), 200)
                    : WithAlpha(Error(), 200);
                DrawSparkline(dl, spMin, spMax,
                              market.price_history, lineCol);
            }

            // Hover glow + click to Market
            if (material::IsRectHovered(cMin, cMax)) {
                dl->AddRect(cMin, cMax, IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance", "hover-glow-alpha").sizeOr(40.0f)),
                            cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f));
                ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                if (ImGui::IsMouseClicked(0))
                    app->setCurrentPage(NavPage::Market);
            }
        }

        // Advance cursor past the card row(s)
        {
            int totalCards = 4;
            int numRows = (totalCards + numCols - 1) / numCols;
            ImGui::Dummy(ImVec2(availWidth, cardH * numRows + cardGap * (numRows - 1)));
        }
        ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
    }

    // ================================================================
    // Address list — DrawList-based rows (matches recent tx style)
    // ================================================================
    {
        // Header row: title only
        Type().text(TypeStyle::H6, TR("your_addresses"));

        ImGui::Spacing();

        // Static filter state (declared here, UI rendered below with ADDRESSES overline)
        static char addr_search[128] = "";
        static bool s_hideZeroBalances = true;
        static bool s_showHidden = false;

        // Build a merged + sorted list of all addresses
        struct AddrRow {
            const AddressInfo* info;
            bool isZ;
            bool hidden;
            bool favorite;
            bool mining;
        };
        std::vector<AddrRow> rows;
        rows.reserve(state.z_addresses.size() + state.t_addresses.size());
        for (const auto& a : state.z_addresses) {
            std::string filter(addr_search);
            if (!containsIgnoreCase(a.address, filter) &&
                !containsIgnoreCase(a.label, filter))
                continue;
            bool isHidden = app->isAddressHidden(a.address);
            if (isHidden && !s_showHidden) continue;
            bool isFav = app->isAddressFavorite(a.address);
            if (s_hideZeroBalances && a.balance < 1e-9 && !isHidden && !isFav)
                continue;
            rows.push_back({&a, true, isHidden, isFav, app->isMiningAddress(a.address)});
        }
        for (const auto& a : state.t_addresses) {
            std::string filter(addr_search);
            if (!containsIgnoreCase(a.address, filter) &&
                !containsIgnoreCase(a.label, filter))
                continue;
            bool isHidden = app->isAddressHidden(a.address);
            if (isHidden && !s_showHidden) continue;
            bool isFav = app->isAddressFavorite(a.address);
            if (s_hideZeroBalances && a.balance < 1e-9 && !isHidden && !isFav)
                continue;
            rows.push_back({&a, false, isHidden, isFav, app->isMiningAddress(a.address)});
        }

        // Sort: favorites first, then Z addresses, then by balance descending
        static int s_sortCol = 3; // default sort by balance
        static bool s_sortAsc = false;
        std::sort(rows.begin(), rows.end(),
            [](const AddrRow& a, const AddrRow& b) -> bool {
                if (a.favorite != b.favorite) return a.favorite > b.favorite;
                if (a.isZ != b.isZ) return a.isZ > b.isZ; // Z first
                if (s_sortAsc)
                    return a.info->balance < b.info->balance;
                else
                    return a.info->balance > b.info->balance;
            });

        // Recent TX gets sizing priority — compute its reserve first,
        // then the address list gets whatever remains.
        float scaledTxRowH = std::max(S.drawElement("tabs.balance", "recent-tx-row-min-height").size, kRecentTxRowHeight * vs);
        // Recent TX section: header + kRecentTxCount rows + status label + gaps
        float recentTxReserve = S.drawElement("tabs.balance", "recent-tx-header-height").size * vs + kRecentTxCount * scaledTxRowH + Layout::spacingXl();

        // Search + create buttons row
        float avail = ImGui::GetContentRegionAvail().x;
        float schemaMaxW = (searchIn.maxWidth >= 0) ? searchIn.maxWidth : 250.0f;
        float schemaRatio = (searchIn.widthRatio >= 0) ? searchIn.widthRatio : 0.30f;
        float searchW = std::min(schemaMaxW * hs, avail * schemaRatio);
        ImGui::SetNextItemWidth(searchW);
        ImGui::InputTextWithHint("##AddrSearch", "Filter...", addr_search, sizeof(addr_search));

        ImGui::SameLine(0, Layout::spacingLg());
        ImGui::Checkbox(TrId("hide_zero_balances", "hide0").c_str(), &s_hideZeroBalances);
        {
            int hc = app->getHiddenAddressCount();
            if (hc > 0) {
                ImGui::SameLine(0, Layout::spacingLg());
                char hlbl[64];
                snprintf(hlbl, sizeof(hlbl), TR("show_hidden"), hc);
                ImGui::Checkbox(hlbl, &s_showHidden);
            } else {
                s_showHidden = false;
            }
        }

        float buttonWidth = (addrBtn.width > 0) ? addrBtn.width : 140.0f;
        float spacing = (addrBtn.gap > 0) ? addrBtn.gap : 8.0f;
        float totalButtonsWidth = buttonWidth * 2 + spacing;
        ImGui::SameLine(std::max(kMinButtonsPosition,
                                 ImGui::GetWindowWidth() - totalButtonsWidth - kButtonRowRightMargin));

        bool addrSyncing = state.sync.syncing && !state.sync.isSynced();
        ImGui::BeginDisabled(addrSyncing);
        if (s_generating_z_address) {
            int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
            const char* dotStr[] = {"", ".", "..", "..."};
            char genLabel[64];
            snprintf(genLabel, sizeof(genLabel), "%s%s##bal_z", TR("generating"), dotStr[dots]);
            TactileButton(genLabel, ImVec2(buttonWidth, 0), S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font));
        } else if (TactileButton(TR("new_z_address"), ImVec2(buttonWidth, 0), S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font))) {
            s_generating_z_address = true;
            app->createNewZAddress([](const std::string& addr) {
                s_generating_z_address = false;
                DEBUG_LOGF("Created new z-address: %s\n", addr.c_str());
            });
        }
        ImGui::SameLine();
        if (TactileButton(TR("new_t_address"), ImVec2(buttonWidth, 0), S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font))) {
            app->createNewTAddress([](const std::string& addr) {
                DEBUG_LOGF("Created new t-address: %s\n", addr.c_str());
            });
        }
        ImGui::EndDisabled();

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

        float availWidth = ImGui::GetContentRegionAvail().x;
        // Address list gets whatever height remains after recent TX reserve
        float classicAddrH = S.drawElement("tabs.balance.classic", "address-table-height").size;
        float addrListH;
        if (classicAddrH >= 0.0f) {
            addrListH = classicAddrH * dp;  // scaled for DPI + font scale
        } else {
            addrListH = ImGui::GetContentRegionAvail().y - recentTxReserve;
        }
        // Keep address list at a reasonable minimum; if too tight,
        // shrink recent-tx reserve instead so both remain visible.
        float addrListMin = S.drawElement("tabs.balance", "addr-list-min-height").sizeOr(40.0f);
        if (addrListH < addrListMin) {
            addrListH = addrListMin;
        }

        // Glass panel wrapping the list area (matching tx list)
        ImDrawList* dlPanel = ImGui::GetWindowDrawList();
        ImVec2 listPanelMin = ImGui::GetCursorScreenPos();
        ImVec2 listPanelMax(listPanelMin.x + availWidth, listPanelMin.y + addrListH);
        GlassPanelSpec addrGlassSpec;
        addrGlassSpec.rounding = glassRound;
        DrawGlassPanel(dlPanel, listPanelMin, listPanelMax, addrGlassSpec);

        // Scroll-edge mask state
        float addrScrollY = 0.0f, addrScrollMaxY = 0.0f;
        int addrParentVtx = dlPanel->VtxBuffer.Size;

        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingLg(), Layout::spacingSm()));
        ImGui::BeginChild("AddressList", ImVec2(availWidth, addrListH), false,
                          ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollWithMouse);
        ApplySmoothScroll();
        ImDrawList* addrChildDL = ImGui::GetWindowDrawList();
        int addrChildVtx = addrChildDL->VtxBuffer.Size;

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

        if (!app->isConnected()) {
            ImGui::Dummy(ImVec2(0, 16 * dp));
            float cw = ImGui::GetContentRegionAvail().x;
            ImVec2 ts = ImGui::CalcTextSize(TR("not_connected"));
            ImGui::SetCursorPosX((cw - ts.x) * 0.5f);
            ImGui::TextDisabled("%s", TR("not_connected"));
        } else if (rows.empty()) {
            // Empty state
            float cw = ImGui::GetContentRegionAvail().x;
            float ch = ImGui::GetContentRegionAvail().y;
            if (ch < 60) ch = 60;

            if (addr_search[0]) {
                ImVec2 textSz = ImGui::CalcTextSize("No matching addresses");
                ImGui::SetCursorPosX((cw - textSz.x) * 0.5f);
                ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ch * 0.25f);
                ImGui::TextDisabled("No matching addresses");
            } else {
                const char* msg = "No addresses yet";
                ImVec2 msgSz = ImGui::CalcTextSize(msg);
                ImGui::SetCursorPosX((cw - msgSz.x) * 0.5f);
                ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ch * 0.25f);
                ImGui::TextDisabled("%s", msg);
            }
        } else {
            // DrawList-based address rows (matching transaction list style)
            ImDrawList* dl = ImGui::GetWindowDrawList();
            ImFont* capFont = Type().caption();
            ImFont* body2 = Type().body2();
            float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg() + Layout::spacingMd();
            static int selected_row = -1;
            addrScrollY    = ImGui::GetScrollY();
            addrScrollMaxY = ImGui::GetScrollMaxY();

            ImU32 greenCol = S.resolveColor("var(--accent-shielded)", Success());
            ImU32 goldCol  = S.resolveColor("var(--accent-transparent)", Warning());
            float rowPadLeft = Layout::cardInnerPadding();
            float rowPadRight = Layout::cardInnerPadding();
            float rowIconSz = std::max(S.drawElement("tabs.balance", "address-icon-min-size").size, S.drawElement("tabs.balance", "address-icon-size").size * hs);
            float innerW = ImGui::GetContentRegionAvail().x;

            for (int row_idx = 0; row_idx < (int)rows.size(); row_idx++) {
                const auto& row = rows[row_idx];
                const auto& addr = *row.info;
                ImVec2 rowPos = ImGui::GetCursorScreenPos();
                ImVec2 rowEnd(rowPos.x + innerW, rowPos.y + rowH);

                ImU32 typeCol = row.isZ ? greenCol : goldCol;
                if (row.hidden) typeCol = OnSurfaceDisabled();

                // Golden border for favorites
                if (row.favorite) {
                    ImU32 favBorder = IM_COL32(255, 200, 50, 120);
                    dl->AddRect(rowPos, rowEnd, favBorder, 4.0f * dp, 0, 1.5f * dp);
                }

                // Selected indicator (left accent bar)
                if (selected_row == row_idx) {
                    ImDrawFlags accentFlags = 0;
                    float accentRound = 2.0f * dp;
                    if (row_idx == 0) {
                        accentFlags = ImDrawFlags_RoundCornersTopLeft;
                        accentRound = glassRound;
                    }
                    if (row_idx == (int)rows.size() - 1) {
                        accentFlags |= ImDrawFlags_RoundCornersBottomLeft;
                        accentRound = glassRound;
                    }
                    dl->AddRectFilled(rowPos, ImVec2(rowPos.x + 3 * dp, rowEnd.y), typeCol, accentRound, accentFlags);
                    dl->AddRectFilled(rowPos, rowEnd, IM_COL32(255, 255, 255, 20), 4.0f * dp);
                }

                // Hover glow
                bool hovered = material::IsRectHovered(rowPos, rowEnd);
                if (hovered && selected_row != row_idx) {
                    dl->AddRectFilled(rowPos, rowEnd, IM_COL32(255, 255, 255, 15), 4.0f * dp);
                    ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                }

                float cx = rowPos.x + rowPadLeft;
                float cy = rowPos.y + Layout::spacingMd();

                // --- Button zone (right edge): [eye] [star] ---
                float btnH = rowH - Layout::spacingSm() * 2.0f;
                float btnW = btnH;
                float btnGap = Layout::spacingSm();
                float btnY = rowPos.y + (rowH - btnH) * 0.5f;
                float rightEdge = rowPos.x + innerW;
                float starX = rightEdge - btnW - rowPadRight;
                float eyeX  = starX - btnGap - btnW;
                float btnRound = 6.0f * dp;
                bool btnClicked = false;

                // Star button (always shown, rightmost)
                {
                    ImVec2 bMin(starX, btnY), bMax(starX + btnW, btnY + btnH);
                    bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
                    ImU32 starFill = row.favorite ? IM_COL32(255, 200, 50, 40) : IM_COL32(255, 255, 255, bHov ? 25 : 12);
                    ImU32 starBorder = row.favorite ? IM_COL32(255, 200, 50, 100) : IM_COL32(255, 255, 255, bHov ? 50 : 25);
                    dl->AddRectFilled(bMin, bMax, starFill, btnRound);
                    dl->AddRect(bMin, bMax, starBorder, btnRound, 0, 1.0f * dp);
                    ImFont* iconFont = material::Type().iconSmall();
                    const char* starIcon = row.favorite ? ICON_MD_STAR : ICON_MD_STAR_BORDER;
                    ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, starIcon);
                    ImU32 starCol = row.favorite ? IM_COL32(255, 200, 50, 255) : (bHov ? OnSurface() : OnSurfaceDisabled());
                    dl->AddText(iconFont, iconFont->LegacySize,
                        ImVec2(starX + (btnW - iSz.x) * 0.5f, btnY + (btnH - iSz.y) * 0.5f), starCol, starIcon);
                    if (bHov && ImGui::IsMouseClicked(0)) {
                        if (row.favorite) app->unfavoriteAddress(addr.address);
                        else app->favoriteAddress(addr.address);
                        btnClicked = true;
                    }
                    if (bHov) ImGui::SetTooltip("%s", row.favorite ? TR("remove_favorite") : TR("favorite_address"));
                }

                // Eye button (zero balance or hidden)
                if (addr.balance < 1e-9 || row.hidden) {
                    ImVec2 bMin(eyeX, btnY), bMax(eyeX + btnW, btnY + btnH);
                    bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
                    ImU32 eyeFill = IM_COL32(255, 255, 255, bHov ? 25 : 12);
                    ImU32 eyeBorder = IM_COL32(255, 255, 255, bHov ? 50 : 25);
                    dl->AddRectFilled(bMin, bMax, eyeFill, btnRound);
                    dl->AddRect(bMin, bMax, eyeBorder, btnRound, 0, 1.0f * dp);
                    ImFont* iconFont = material::Type().iconSmall();
                    const char* hideIcon = row.hidden ? ICON_MD_VISIBILITY : ICON_MD_VISIBILITY_OFF;
                    ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, hideIcon);
                    ImU32 iconCol = bHov ? OnSurface() : OnSurfaceDisabled();
                    dl->AddText(iconFont, iconFont->LegacySize,
                        ImVec2(eyeX + (btnW - iSz.x) * 0.5f, btnY + (btnH - iSz.y) * 0.5f), iconCol, hideIcon);
                    if (bHov && ImGui::IsMouseClicked(0)) {
                        if (row.hidden) app->unhideAddress(addr.address);
                        else app->hideAddress(addr.address);
                        btnClicked = true;
                    }
                    if (bHov) ImGui::SetTooltip("%s", row.hidden ? TR("restore_address") : TR("hide_address"));
                }

                // Content zone ends before buttons
                float contentRight = eyeX - Layout::spacingSm();

                // Type icon (shield for Z, circle for T)
                float iconCx = cx + rowIconSz;
                float iconCy = cy + body2->LegacySize * 0.5f;
                if (row.isZ) {
                    ImFont* iconFont = material::Type().iconSmall();
                    const char* shieldIcon = ICON_MD_SHIELD;
                    ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, shieldIcon);
                    dl->AddText(iconFont, iconFont->LegacySize,
                        ImVec2(iconCx - iSz.x * 0.5f, iconCy - iSz.y * 0.5f), typeCol, shieldIcon);
                } else {
                    ImFont* iconFont = material::Type().iconSmall();
                    const char* circIcon = ICON_MD_CIRCLE;
                    ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, circIcon);
                    dl->AddText(iconFont, iconFont->LegacySize,
                        ImVec2(iconCx - iSz.x * 0.5f, iconCy - iSz.y * 0.5f), typeCol, circIcon);
                }

                // Type label (first line, next to icon)
                float labelX = cx + rowIconSz * 2.0f + Layout::spacingSm();
                const char* typeLabel = row.isZ ? "Shielded" : "Transparent";
                const char* hiddenTag = row.hidden ? " (hidden)" : "";
                const char* viewOnlyTag = (!addr.has_spending_key) ? " (view-only)" : "";
                const char* miningTag = row.mining ? TR("mining_tag") : "";
                char typeBuf[64];
                snprintf(typeBuf, sizeof(typeBuf), "%s%s%s%s", typeLabel, hiddenTag, viewOnlyTag, miningTag);
                dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), typeCol, typeBuf);

                // Label (if present, next to type)
                if (!addr.label.empty()) {
                    float typeLabelW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, typeBuf).x;
                    dl->AddText(capFont, capFont->LegacySize,
                                ImVec2(labelX + typeLabelW + Layout::spacingLg(), cy),
                                OnSurfaceMedium(), addr.label.c_str());
                }

                // Address (second line) — show full if it fits, otherwise truncate
                float addrAvailW = contentRight - labelX;
                ImVec2 fullAddrSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0.0f, addr.address.c_str());
                std::string display_addr;
                if (fullAddrSz.x <= addrAvailW) {
                    display_addr = addr.address;
                } else {
                    int addrTruncLen = (addrTable.columns.count("address") && addrTable.columns.at("address").truncate > 0)
                        ? addrTable.columns.at("address").truncate : 32;
                    display_addr = truncateAddress(addr.address, addrTruncLen);
                }
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(labelX, cy + body2->LegacySize + Layout::spacingXs()),
                            OnSurfaceMedium(), display_addr.c_str());

                // Balance (right-aligned within content zone)
                char balBuf[32];
                snprintf(balBuf, sizeof(balBuf), "%.8f", addr.balance);
                ImVec2 balSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, balBuf);
                float balX = contentRight - balSz.x;
                ImU32 balCol = addr.balance > 0.0
                    ? (row.isZ ? greenCol : OnSurface())
                    : OnSurfaceDisabled();
                if (row.hidden) balCol = OnSurfaceDisabled();
                DrawTextShadow(dl, body2, body2->LegacySize, ImVec2(balX, cy), balCol, balBuf);

                // USD equivalent (right-aligned, second line)
                double priceUsd = state.market.price_usd;
                if (priceUsd > 0.0 && addr.balance > 0.0) {
                    char usdBuf[32];
                    double usdVal = addr.balance * priceUsd;
                    if (usdVal >= 1.0)
                        snprintf(usdBuf, sizeof(usdBuf), "$%.2f", usdVal);
                    else if (usdVal >= 0.01)
                        snprintf(usdBuf, sizeof(usdBuf), "$%.4f", usdVal);
                    else
                        snprintf(usdBuf, sizeof(usdBuf), "$%.6f", usdVal);
                    ImVec2 usdSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, usdBuf);
                    dl->AddText(capFont, capFont->LegacySize,
                                ImVec2(contentRight - usdSz.x,
                                       cy + body2->LegacySize + Layout::spacingXs()),
                                OnSurfaceDisabled(), usdBuf);
                }

                // Click to copy + select
                if (hovered && ImGui::IsMouseClicked(0) && !btnClicked) {
                    ImGui::SetClipboardText(addr.address.c_str());
                    selected_row = row_idx;
                }

                // Invisible button for context menu + tooltip
                ImGui::PushID(row_idx);
                ImGui::InvisibleButton("##addr", ImVec2(innerW, rowH));

                // Tooltip with full address
                if (ImGui::IsItemHovered() && !btnClicked) {
                    ImGui::SetTooltip("%s", addr.address.c_str());
                }

                // Right-click context menu
                const auto& acrTheme = GetCurrentAcrylicTheme();
                if (effects::ImGuiAcrylic::BeginAcrylicContextItem("AddressContext", 0, acrTheme.menu)) {
                    if (ImGui::MenuItem(TR("copy_address"))) {
                        ImGui::SetClipboardText(addr.address.c_str());
                    }
                    if (ImGui::MenuItem(TR("send_from_this_address"))) {
                        SetSendFromAddress(addr.address);
                        app->setCurrentPage(NavPage::Send);
                    }
                    ImGui::Separator();
                    if (ImGui::MenuItem(row.mining ? TR("unmark_mining_address") : TR("mark_mining_address"))) {
                        app->setMiningAddress(addr.address, !row.mining);
                    }
                    if (ImGui::MenuItem(TR("export_private_key"))) {
                        KeyExportDialog::show(addr.address, KeyExportDialog::KeyType::Private);
                    }
                    if (row.isZ) {
                        if (ImGui::MenuItem(TR("export_viewing_key"))) {
                            KeyExportDialog::show(addr.address, KeyExportDialog::KeyType::Viewing);
                        }
                    }
                    if (ImGui::MenuItem(TR("show_qr_code"))) {
                        QRPopupDialog::show(addr.address,
                                            row.isZ ? "Z-Address" : "T-Address");
                    }
                    ImGui::Separator();
                    if (row.hidden) {
                        if (ImGui::MenuItem(TR("restore_address")))
                            app->unhideAddress(addr.address);
                    } else if (addr.balance < 1e-9) {
                        if (ImGui::MenuItem(TR("hide_address")))
                            app->hideAddress(addr.address);
                    }
                    if (row.favorite) {
                        if (ImGui::MenuItem(TR("remove_favorite")))
                            app->unfavoriteAddress(addr.address);
                    } else {
                        if (ImGui::MenuItem(TR("favorite_address")))
                            app->favoriteAddress(addr.address);
                    }
                    effects::ImGuiAcrylic::EndAcrylicPopup();
                }
                ImGui::PopID();

                // Subtle divider between rows (matching tx list)
                if (row_idx < (int)rows.size() - 1 && selected_row != row_idx) {
                    ImVec2 divStart = ImGui::GetCursorScreenPos();
                    dl->AddLine(
                        ImVec2(divStart.x + rowPadLeft + rowIconSz * 2.0f, divStart.y),
                        ImVec2(divStart.x + innerW - Layout::spacingLg(), divStart.y),
                        IM_COL32(255, 255, 255, 15));
                }
            }
        }

        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
        ImGui::EndChild();
        ImGui::PopStyleVar(); // WindowPadding for address list

        // CSS-style clipping mask (same as history list)
        {
            float fadeZone = std::min(
                (Type().body2()->LegacySize + Type().caption()->LegacySize + Layout::spacingLg() + Layout::spacingMd()) * 1.2f,
                addrListH * 0.18f);
            ApplyScrollEdgeMask(dlPanel, addrParentVtx, addrChildDL, addrChildVtx,
                                listPanelMin.y, listPanelMax.y, fadeZone, addrScrollY, addrScrollMaxY);
        }

        // Status line (matching tx list)
        {
            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
            char countBuf[128];
            int totalAddrs = (int)(state.z_addresses.size() + state.t_addresses.size());
            snprintf(countBuf, sizeof(countBuf), "Showing %d of %d addresses",
                     (int)rows.size(), totalAddrs);
            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), countBuf);
        }
    }

    // ================================================================
    // Recent Transactions
    // ================================================================
    {
        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "RECENT TRANSACTIONS");
        ImGui::SameLine();
        if (TactileSmallButton("View All", S.resolveFont(actionBtn.font.empty() ? "button" : actionBtn.font))) {
            app->setCurrentPage(NavPage::History);
        }
        ImGui::Spacing();

        ImFont* capFont = Type().caption();
        float rowH = std::max(18.0f * dp, kRecentTxRowHeight * vs);
        float listH = std::max(rowH, ImGui::GetContentRegionAvail().y);
        ImGui::BeginChild("##BalanceClassicRecentRows", ImVec2(0, listH), false,
            ImGuiWindowFlags_NoBackground);

        const auto& txs = state.transactions;
        int count = (int)txs.size();
        if (count == 0) {
            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
                               "No transactions yet");
        } else {
            ImDrawList* dl = ImGui::GetWindowDrawList();
            float iconSz = std::max(S.drawElement("tabs.balance", "recent-tx-icon-min-size").size, S.drawElement("tabs.balance", "recent-tx-icon-size").size * hs);

            for (int i = 0; i < count; i++) {
                const auto& tx = txs[i];
                ImVec2 rowPos = ImGui::GetCursorScreenPos();
                float rowY = rowPos.y + rowH * 0.5f;

                auto display = buildRecentTxDisplay(tx, (int)S.drawElement("tabs.balance", "recent-tx-addr-trunc").sizeOr(20.0f));
                DrawTxIcon(dl, tx.type, rowPos.x + Layout::spacingMd(), rowY, iconSz, recentTxIconColor(tx.type));

                // Type label
                float tx_x = rowPos.x + Layout::spacingMd() + iconSz * 2.0f + Layout::spacingSm();
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(tx_x, rowPos.y + 2 * dp),
                            OnSurfaceMedium(), display.typeText.c_str());

                // Address (truncated)
                float addrX = tx_x + S.drawElement("tabs.balance", "recent-tx-addr-offset").sizeOr(65.0f);
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(addrX, rowPos.y + 2 * dp),
                            OnSurfaceDisabled(), display.addressText.c_str());

                // Amount (right-aligned area)
                ImVec2 amtSz = capFont->CalcTextSizeA(
                    capFont->LegacySize, 10000, 0, display.amountText.c_str());
                float rightEdge = rowPos.x + ImGui::GetContentRegionAvail().x;
                float amtX = rightEdge - amtSz.x - std::max(S.drawElement("tabs.balance", "amount-right-min-margin").size, S.drawElement("tabs.balance", "amount-right-margin").size * hs);
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(amtX, rowPos.y + 2 * dp),
                            recentTxAmountColor(tx.type),
                            display.amountText.c_str());

                // Time ago
                ImVec2 agoSz = capFont->CalcTextSizeA(
                    capFont->LegacySize, 10000, 0, display.timeText.c_str());
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(rightEdge - agoSz.x - S.drawElement("tabs.balance", "recent-tx-time-margin").sizeOr(4.0f), rowPos.y + 2 * dp),
                            OnSurfaceDisabled(), display.timeText.c_str());

                // Clickable row — hover highlight + navigate to History
                float rowW = ImGui::GetContentRegionAvail().x;
                ImVec2 rowEnd(rowPos.x + rowW, rowPos.y + rowH);
                if (material::IsRectHovered(rowPos, rowEnd)) {
                    dl->AddRectFilled(rowPos, rowEnd,
                                      IM_COL32(255, 255, 255, 15), S.drawElement("tabs.balance", "row-hover-rounding").sizeOr(4.0f));
                    ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                    if (ImGui::IsMouseClicked(0))
                        app->setCurrentPage(NavPage::History);
                }

                ImGui::Dummy(ImVec2(0, rowH));
            }
        }
        ImGui::EndChild();
    }
}

// ============================================================================
// Shared helpers used by multiple layouts
// ============================================================================

// Update animated lerp balances — called at the top of every layout
static void UpdateBalanceLerp(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const float kBalanceLerpSpeed = S.drawElement("tabs.balance", "balance-lerp-speed").sizeOr(8.0f);
    const auto& state = app->state();
    float dt = ImGui::GetIO().DeltaTime;
    float speed = (app->settings() && app->settings()->getReduceMotion()) ? 999.0f : kBalanceLerpSpeed;
    auto lerp = [](double& disp, double target, float dt, float spd) {
        double diff = target - disp;
        if (std::abs(diff) < 1e-9) { disp = target; return; }
        disp += diff * (double)(dt * spd);
        if (std::abs(target - disp) < 1e-9) disp = target;
    };
    lerp(s_dispTotal,       app->getTotalBalance(),       dt, speed);
    lerp(s_dispShielded,    app->getShieldedBalance(),    dt, speed);
    lerp(s_dispTransparent, app->getTransparentBalance(), dt, speed);
    lerp(s_dispUnconfirmed, state.unconfirmed_balance,    dt, speed);
}

// Render compact hero line: logo + balance + USD + mining on one line
static void RenderCompactHero(App* app, ImDrawList* dl, float availW, float hs, float vs, float heroHeightOverride = -1.0f) {
    using namespace material;
    char buf[64];
    const float dp = Layout::dpiScale();

    // Coin logo
    ImTextureID logoTex = app->getCoinLogoTexture();
    ImFont* sub1 = Type().subtitle1();
    float logoSz = sub1->LegacySize + 4.0f * dp;
    float lineH = (heroHeightOverride >= 0.0f) ? heroHeightOverride : logoSz;
    if (logoTex != 0) {
        ImVec2 pos = ImGui::GetCursorScreenPos();
        dl->AddImage(logoTex,
            ImVec2(pos.x, pos.y),
            ImVec2(pos.x + lineH, pos.y + lineH),
            ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 255));
        ImGui::Dummy(ImVec2(lineH + Layout::spacingSm(), lineH));
        ImGui::SameLine();
    }

    // Total balance
    snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
    ImVec2 pos = ImGui::GetCursorScreenPos();
    float fontSize = (heroHeightOverride >= 0.0f) ? heroHeightOverride : sub1->LegacySize;
    DrawTextShadow(dl, sub1, fontSize, pos, OnSurface(), buf);
    ImVec2 sz = sub1->CalcTextSizeA(fontSize, 10000.0f, 0.0f, buf);
    ImGui::Dummy(ImVec2(sz.x, lineH));
    ImGui::SameLine();

    // Ticker
    ImFont* capFont = Type().caption();
    float tickerFontSize = (heroHeightOverride >= 0.0f) ? heroHeightOverride * 0.6f : capFont->LegacySize;
    float tickerY = pos.y + lineH - tickerFontSize;
    dl->AddText(capFont, tickerFontSize,
                ImVec2(ImGui::GetCursorScreenPos().x, tickerY),
                OnSurfaceMedium(), DRAGONX_TICKER);
    ImGui::Dummy(ImVec2(capFont->CalcTextSizeA(tickerFontSize, 10000, 0, DRAGONX_TICKER).x + Layout::spacingLg(), lineH));
    ImGui::SameLine();

    // USD value
    const auto& state = app->state();
    double usd_value = state.getBalanceUSD();
    if (usd_value > 0.0)
        snprintf(buf, sizeof(buf), "$%.2f", usd_value);
    else
        snprintf(buf, sizeof(buf), "$--.--");
    dl->AddText(capFont, tickerFontSize,
                ImVec2(ImGui::GetCursorScreenPos().x, tickerY),
                OnSurfaceDisabled(), buf);

    ImGui::NewLine();
}

// Render the shared address list section (used by all layouts)
static void RenderSharedAddressList(App* app, float listH, float availW,
                                     float glassRound, float hs, float vs) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const float dp = Layout::dpiScale();
    const auto addrBtn   = S.button("tabs.balance", "address-button");
    const auto actionBtn = S.button("tabs.balance", "action-button");
    const auto searchIn  = S.input("tabs.balance", "search-input");
    const auto addrTable = S.table("tabs.balance", "address-table");
    const auto& state = app->state();

    Type().text(TypeStyle::H6, TR("your_addresses"));
    ImGui::Spacing();

    // ---- Persistent state ----
    static char addr_search[128] = "";
    static bool s_hideZeroBalances = true;
    static bool s_showHidden = false;

    // Drag state
    static int  s_dragIdx = -1;         // row being dragged (-1 = none)
    static float s_dragOffsetY = 0.0f;  // mouse offset from row top
    static float s_dragStartY = 0.0f;   // mouse Y at drag start
    static bool  s_dragActive = false;  // drag distance threshold passed
    static int   s_dropTargetIdx = -1;  // row hovered during drag

    // Copy feedback
    static int   s_copiedRow = -1;
    static float s_copiedTime = 0.0f;

    // ---- Build and filter address rows ----
    std::vector<AddressListInput> rowInputs;
    rowInputs.reserve(state.z_addresses.size() + state.t_addresses.size());

    auto addRows = [&](const auto& addrs, bool isZ) {
        for (const auto& a : addrs) {
            std::string addrLabel = app->getAddressLabel(a.address);
            bool isHidden = app->isAddressHidden(a.address);
            bool isFav = app->isAddressFavorite(a.address);
            bool isMining = app->isMiningAddress(a.address);
            rowInputs.push_back({&a, isZ, isHidden, isFav, isMining,
                                 addrLabel, app->getAddressIcon(a.address),
                                 app->getAddressSortOrder(a.address)});
        }
    };
    addRows(state.z_addresses, true);
    addRows(state.t_addresses, false);
    auto rows = BuildAddressListRows(rowInputs, addr_search, s_hideZeroBalances, s_showHidden);

    // ---- Toolbar: search + checkboxes + create buttons ----
    float avail = ImGui::GetContentRegionAvail().x;
    float schemaMaxW = (searchIn.maxWidth >= 0) ? searchIn.maxWidth : 250.0f;
    float schemaRatio = (searchIn.widthRatio >= 0) ? searchIn.widthRatio : 0.30f;
    float searchW = std::min(schemaMaxW * hs, avail * schemaRatio);
    ImGui::SetNextItemWidth(searchW);
    ImGui::InputTextWithHint("##AddrSearch", TR("filter"), addr_search, sizeof(addr_search));
    ImGui::SameLine(0, Layout::spacingLg());
    ImGui::Checkbox(TrId("hide_zero_balances", "hide0_v2").c_str(), &s_hideZeroBalances);
    {
        int hc = app->getHiddenAddressCount();
        if (hc > 0) {
            ImGui::SameLine(0, Layout::spacingLg());
            char hlbl[64];
            snprintf(hlbl, sizeof(hlbl), TR("show_hidden"), hc);
            ImGui::Checkbox(hlbl, &s_showHidden);
        } else {
            s_showHidden = false;
        }
    }

    float buttonWidth = (addrBtn.width > 0) ? addrBtn.width : 140.0f;
    float spacing = (addrBtn.gap > 0) ? addrBtn.gap : 8.0f;
    float totalButtonsWidth = buttonWidth * 2 + spacing;
    float kMinButtonsPosition = std::max(S.drawElement("tabs.balance", "min-buttons-position").size,
                                          S.drawElement("tabs.balance", "buttons-position").size * hs);
    ImGui::SameLine(std::max(kMinButtonsPosition,
                             ImGui::GetWindowWidth() - totalButtonsWidth -
                             S.drawElement("tabs.balance", "button-row-right-margin").sizeOr(16.0f)));

    bool sharedAddrSyncing = state.sync.syncing && !state.sync.isSynced();
    ImGui::BeginDisabled(sharedAddrSyncing);
    if (s_generating_z_address) {
        int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
        const char* dotStr[] = {"", ".", "..", "..."};
        char genLabel[64];
        snprintf(genLabel, sizeof(genLabel), "%s%s##shared_z", TR("generating"), dotStr[dots]);
        TactileButton(genLabel, ImVec2(buttonWidth, 0),
                      S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font));
    } else if (TactileButton(TR("new_z_address"), ImVec2(buttonWidth, 0),
                      S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font))) {
        s_generating_z_address = true;
        app->createNewZAddress([](const std::string& addr) {
            s_generating_z_address = false;
            DEBUG_LOGF("Created new z-address: %s\n", addr.c_str());
        });
    }
    ImGui::SameLine();
    if (TactileButton(TR("new_t_address"), ImVec2(buttonWidth, 0),
                      S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font))) {
        app->createNewTAddress([](const std::string& addr) {
            DEBUG_LOGF("Created new t-address: %s\n", addr.c_str());
        });
    }
    ImGui::EndDisabled();

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

    // ---- Glass panel container ----
    float addrListH = listH;
    if (addrListH < 40.0f * dp) addrListH = 40.0f * dp;

    ImDrawList* dlPanel = ImGui::GetWindowDrawList();
    ImVec2 listPanelMin = ImGui::GetCursorScreenPos();
    ImVec2 listPanelMax(listPanelMin.x + availW, listPanelMin.y + addrListH);
    GlassPanelSpec addrGlassSpec;
    addrGlassSpec.rounding = glassRound;
    DrawGlassPanel(dlPanel, listPanelMin, listPanelMax, addrGlassSpec);

    float addrScrollY = 0.0f, addrScrollMaxY = 0.0f;
    int addrParentVtx = dlPanel->VtxBuffer.Size;

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingLg(), Layout::spacingSm()));
    ImGui::BeginChild("AddressList", ImVec2(availW, addrListH), false,
                      ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollWithMouse);
    ApplySmoothScroll();
    ImDrawList* addrChildDL = ImGui::GetWindowDrawList();
    int addrChildVtx = addrChildDL->VtxBuffer.Size;

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

    // ---- Empty states ----
    if (!app->isConnected()) {
        ImGui::Dummy(ImVec2(0, 16 * dp));
        float cw = ImGui::GetContentRegionAvail().x;
        ImVec2 ts = ImGui::CalcTextSize(TR("not_connected"));
        ImGui::SetCursorPosX((cw - ts.x) * 0.5f);
        ImGui::TextDisabled("%s", TR("not_connected"));
    } else if (rows.empty()) {
        float cw = ImGui::GetContentRegionAvail().x;
        float ch = ImGui::GetContentRegionAvail().y;
        if (ch < 60) ch = 60;
        const char* emptyMsg = addr_search[0] ? TR("no_addresses_match") : TR("no_addresses_yet");
        ImVec2 msgSz = ImGui::CalcTextSize(emptyMsg);
        ImGui::SetCursorPosX((cw - msgSz.x) * 0.5f);
        ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ch * 0.25f);
        ImGui::TextDisabled("%s", emptyMsg);
    } else {
        // ---- PASS 1: Compute row layout ----
        ImDrawList* dl = ImGui::GetWindowDrawList();
        ImFont* capFont = Type().caption();
        ImFont* body2 = Type().body2();
        float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg() + Layout::spacingMd() + Layout::spacingMd();
        static int selected_row = -1;
        addrScrollY    = ImGui::GetScrollY();
        addrScrollMaxY = ImGui::GetScrollMaxY();
        ImU32 greenCol = S.resolveColor("var(--accent-shielded)", Success());
        ImU32 goldCol  = S.resolveColor("var(--accent-transparent)", Warning());
        float rowPadLeft = Layout::cardInnerPadding();
        float rowPadRight = Layout::cardInnerPadding();
        float rowIconSz = std::max(S.drawElement("tabs.balance", "address-icon-min-size").size,
                                    S.drawElement("tabs.balance", "address-icon-size").size * hs);
        float innerW = ImGui::GetContentRegionAvail().x;

        // Theme colors for buttons (resolved once)
        ImU32 favGoldFill   = S.resolveColor("var(--favorite-fill)", IM_COL32(255, 200, 50, 40));
        ImU32 favGoldBorder = S.resolveColor("var(--favorite-border)", IM_COL32(255, 200, 50, 100));
        ImU32 favGoldIcon   = S.resolveColor("var(--favorite-icon)", IM_COL32(255, 200, 50, 255));
        ImU32 favGoldOutline= S.resolveColor("var(--favorite-outline)", IM_COL32(255, 200, 50, 120));
        ImU32 btnFill       = S.resolveColor("var(--row-button-fill)", IM_COL32(255, 255, 255, 12));
        ImU32 btnFillHov    = S.resolveColor("var(--row-button-fill-hover)", IM_COL32(255, 255, 255, 25));
        ImU32 btnBorder     = S.resolveColor("var(--row-button-border)", IM_COL32(255, 255, 255, 25));
        ImU32 btnBorderHov  = S.resolveColor("var(--row-button-border-hover)", IM_COL32(255, 255, 255, 50));
        ImU32 rowHoverCol   = S.resolveColor("var(--row-hover)", IM_COL32(255, 255, 255, 15));
        ImU32 rowSelectCol  = S.resolveColor("var(--row-select)", IM_COL32(255, 255, 255, 20));
        ImU32 dividerCol    = S.resolveColor("var(--sidebar-divider)", IM_COL32(255, 255, 255, 15));

        // Compute Y positions for all rows (pass 1)
        std::vector<float> rowY(rows.size());
        float cursorStartY = ImGui::GetCursorScreenPos().y;
        for (int i = 0; i < (int)rows.size(); ++i)
            rowY[i] = cursorStartY + i * rowH;

        // Drag logic — detect drag start, compute drag position
        ImVec2 mousePos = ImGui::GetMousePos();
        bool mouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
        bool mouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);

        // Reset drop target each frame — it gets set only if mouse is over a row.
        // Preserve the drop target on the release frame so the drop handler can use it.
        if (s_dragActive && mouseDown) s_dropTargetIdx = -1;

        if (s_dragIdx >= 0 && !mouseDown) {
            // Mouse released — copy if it was a click (no drag activated)
            if (!s_dragActive && s_dragIdx >= 0 && s_dragIdx < (int)rows.size()) {
                const auto& clickRow = rows[s_dragIdx];
                ImGui::SetClipboardText(clickRow.info->address.c_str());
                selected_row = s_dragIdx;
                s_copiedRow = s_dragIdx;
                s_copiedTime = (float)ImGui::GetTime();
            }
            // Drop
            if (s_dragActive && s_dropTargetIdx >= 0 && s_dropTargetIdx != s_dragIdx) {
                const auto& srcRow = rows[s_dragIdx];
                const auto& dstRow = rows[s_dropTargetIdx];
                // Transfer: if dropped on another row and drag was active
                if (srcRow.info->balance > 1e-9) {
                    AddressTransferDialog::TransferInfo ti;
                    ti.fromAddr = srcRow.info->address;
                    ti.toAddr = dstRow.info->address;
                    ti.fromBalance = srcRow.info->balance;
                    ti.toBalance = dstRow.info->balance;
                    ti.fromIsZ = srcRow.isZ;
                    ti.toIsZ = dstRow.isZ;
                    AddressTransferDialog::show(app, ti);
                }
            } else if (s_dragActive && s_dropTargetIdx < 0) {
                // Reorder: dropped in gap — compute insert position from mouseY
                int insertIdx = 0;
                for (int i = 0; i < (int)rows.size(); ++i) {
                    if (mousePos.y > rowY[i] + rowH * 0.5f) insertIdx = i + 1;
                }
                if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1) {
                    int targetIdx = (insertIdx > s_dragIdx) ? insertIdx - 1 : insertIdx;
                    if (targetIdx >= 0 && targetIdx < (int)rows.size()) {
                        app->swapAddressOrder(rows[s_dragIdx].info->address,
                                              rows[targetIdx].info->address);
                    }
                }
            }
            s_dragIdx = -1;
            s_dragActive = false;
            s_dropTargetIdx = -1;
        }

        // ---- PASS 2: Render rows ----
        for (int row_idx = 0; row_idx < (int)rows.size(); row_idx++) {
            const auto& row = rows[row_idx];
            const auto& addr = *row.info;
            bool isDragged = (s_dragIdx == row_idx && s_dragActive);

            // For dragged row, offset Y to follow mouse
            float drawY = rowY[row_idx];
            if (isDragged) {
                drawY = mousePos.y - s_dragOffsetY;
            }

            ImVec2 rowPos(ImGui::GetCursorScreenPos().x, drawY);
            ImVec2 rowEnd(rowPos.x + innerW, drawY + rowH);
            ImU32 typeCol = row.isZ ? greenCol : goldCol;
            if (row.hidden) typeCol = OnSurfaceDisabled();

            // Dragged row: draw with semi-transparent elevation
            if (isDragged) {
                dl->AddRectFilled(ImVec2(rowPos.x - 2*dp, rowPos.y - 2*dp),
                    ImVec2(rowEnd.x + 2*dp, rowEnd.y + 2*dp),
                    IM_COL32(0, 0, 0, 30), 6.0f * dp);
                dl->AddRectFilled(rowPos, rowEnd, IM_COL32(30, 30, 40, 120), 4.0f * dp);
                // Tooltip following cursor — show transfer intent if over a target row
                if (s_dropTargetIdx >= 0 && s_dropTargetIdx < (int)rows.size()) {
                    const auto& target = rows[s_dropTargetIdx];
                    ImGui::SetTooltip("%s\n%s\n\n%s %s",
                        truncateAddress(addr.address, 32).c_str(),
                        row.isZ ? TR("shielded") : TR("transparent"),
                        TR("transfer_to"),
                        truncateAddress(target.info->address, 32).c_str());
                } else {
                    ImGui::SetTooltip("%s\n%s",
                        truncateAddress(addr.address, 32).c_str(),
                        row.isZ ? TR("shielded") : TR("transparent"));
                }
            }

            // Golden border for favorites — inset so it doesn't touch container
            if (row.favorite) {
                float inset = 2.0f * dp;
                dl->AddRect(ImVec2(rowPos.x + inset, rowPos.y + inset),
                            ImVec2(rowEnd.x - inset, rowEnd.y - inset),
                            favGoldOutline, 4.0f * dp, 0, 1.5f * dp);
            }

            // Selection highlight
            if (selected_row == row_idx && !isDragged) {
                ImDrawFlags accentFlags = 0;
                float accentRound = 2.0f * dp;
                if (row_idx == 0) { accentFlags = ImDrawFlags_RoundCornersTopLeft; accentRound = glassRound; }
                if (row_idx == (int)rows.size() - 1) { accentFlags |= ImDrawFlags_RoundCornersBottomLeft; accentRound = glassRound; }
                dl->AddRectFilled(rowPos, ImVec2(rowPos.x + 3 * dp, rowEnd.y), typeCol, accentRound, accentFlags);
                dl->AddRectFilled(rowPos, rowEnd, rowSelectCol, 4.0f * dp);
            }

            // Hover effects
            bool hovered = !isDragged && material::IsRectHovered(rowPos, rowEnd);

            // Drop target highlight when dragging another row over this one
            if (s_dragActive && s_dragIdx != row_idx && material::IsRectHovered(rowPos, rowEnd)) {
                s_dropTargetIdx = row_idx;
                ImU32 dropCol = WithAlpha(Primary(), 40);
                dl->AddRectFilled(rowPos, rowEnd, dropCol, 4.0f * dp);
                dl->AddRect(rowPos, rowEnd, WithAlpha(Primary(), 140), 4.0f * dp, 0, 2.0f * dp);
            }

            if (hovered && selected_row != row_idx && !s_dragActive) {
                dl->AddRectFilled(rowPos, rowEnd, rowHoverCol, 4.0f * dp);
                ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
            }

            auto rowLayout = ComputeAddressRowLayout(
                rowPos.x, rowPos.y, innerW, rowH, rowPadLeft, rowIconSz,
                Layout::spacingMd(), Layout::spacingSm(), Layout::spacingXs());
            float cx = rowLayout.contentStartX;
            float cy = rowLayout.contentStartY;

            // ---- Button zone (right edge): [eye] [star] ----
            float btnRound = 6.0f * dp;
            bool btnClicked = false;

            if (!isDragged) {
                // Star button
                {
                    const auto& starRect = rowLayout.favoriteButton;
                    ImVec2 bMin(starRect.x, starRect.y), bMax(starRect.x + starRect.width, starRect.y + starRect.height);
                    bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
                    dl->AddRectFilled(bMin, bMax, row.favorite ? favGoldFill : (bHov ? btnFillHov : btnFill), btnRound);
                    dl->AddRect(bMin, bMax, row.favorite ? favGoldBorder : (bHov ? btnBorderHov : btnBorder), btnRound, 0, 1.0f * dp);
                    ImFont* iconFont = Type().iconSmall();
                    const char* starIcon = row.favorite ? ICON_MD_STAR : ICON_MD_STAR_BORDER;
                    ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, starIcon);
                    ImU32 starCol = row.favorite ? favGoldIcon : (bHov ? OnSurface() : OnSurfaceDisabled());
                    dl->AddText(iconFont, iconFont->LegacySize,
                        ImVec2(starRect.x + (starRect.width - iSz.x) * 0.5f,
                               starRect.y + (starRect.height - iSz.y) * 0.5f), starCol, starIcon);
                    if (bHov && mouseClicked) {
                        if (row.favorite) app->unfavoriteAddress(addr.address);
                        else app->favoriteAddress(addr.address);
                        btnClicked = true;
                    }
                    if (bHov) ImGui::SetTooltip("%s", row.favorite ? TR("remove_favorite") : TR("favorite_address"));
                }

                // Eye button (zero balance or hidden)
                bool showEye = true;
                // Always reserve space for both buttons so content doesn't shift
                float contentRight = rowLayout.contentRight;

                if (showEye) {
                    const auto& eyeRect = rowLayout.visibilityButton;
                    ImVec2 bMin(eyeRect.x, eyeRect.y), bMax(eyeRect.x + eyeRect.width, eyeRect.y + eyeRect.height);
                    bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
                    dl->AddRectFilled(bMin, bMax, bHov ? btnFillHov : btnFill, btnRound);
                    dl->AddRect(bMin, bMax, bHov ? btnBorderHov : btnBorder, btnRound, 0, 1.0f * dp);
                    ImFont* iconFont = Type().iconSmall();
                    const char* hideIcon = row.hidden ? ICON_MD_VISIBILITY : ICON_MD_VISIBILITY_OFF;
                    ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, hideIcon);
                    ImU32 iconCol = bHov ? OnSurface() : OnSurfaceDisabled();
                    dl->AddText(iconFont, iconFont->LegacySize,
                        ImVec2(eyeRect.x + (eyeRect.width - iSz.x) * 0.5f,
                               eyeRect.y + (eyeRect.height - iSz.y) * 0.5f), iconCol, hideIcon);
                    if (bHov && mouseClicked) {
                        if (row.hidden) app->unhideAddress(addr.address);
                        else app->hideAddress(addr.address);
                        btnClicked = true;
                    }
                    if (bHov) ImGui::SetTooltip("%s", row.hidden ? TR("restore_address") : TR("hide_address"));
                }

                // ---- Type icon or custom icon ----
                float iconCx = cx + rowIconSz;
                float iconCy = cy + body2->LegacySize * 0.5f;
                {
                    ImFont* iconFont = Type().iconSmall();
                    bool drewCustom = false;
                    if (!row.icon.empty()) {
                        drewCustom = AddressLabelDialog::drawIconByName(
                            dl, row.icon, ImVec2(iconCx, iconCy), iconFont->LegacySize,
                            OnSurfaceMedium(), iconFont, iconFont->LegacySize);
                    }
                    if (!drewCustom) {
                        const char* glyph = row.isZ ? ICON_MD_SHIELD : ICON_MD_CIRCLE;
                        ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, glyph);
                        dl->AddText(iconFont, iconFont->LegacySize,
                            ImVec2(iconCx - iSz.x * 0.5f, iconCy - iSz.y * 0.5f), typeCol, glyph);
                    }
                }

                // ---- Type label (first line) ----
                float labelX = rowLayout.labelX;
                {
                    const char* typeLabel = row.isZ ? TR("shielded") : TR("transparent");
                    const char* hiddenTag = row.hidden ? TR("hidden_tag") : "";
                    const char* miningTag = row.mining ? TR("mining_tag") : "";
                    char typeBuf[64];
                    snprintf(typeBuf, sizeof(typeBuf), "%s%s%s", typeLabel, hiddenTag, miningTag);
                    dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), typeCol, typeBuf);

                    // User label next to type
                    if (!row.label.empty()) {
                        float typeLabelW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, typeBuf).x;
                        dl->AddText(capFont, capFont->LegacySize,
                                    ImVec2(labelX + typeLabelW + Layout::spacingLg(), cy),
                                    OnSurfaceMedium(), row.label.c_str());
                    }
                }

                // ---- Address (second line) ----
                {
                    float addrAvailW = contentRight - labelX;
                    ImVec2 fullAddrSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0.0f, addr.address.c_str());
                    std::string display_addr;
                    if (fullAddrSz.x <= addrAvailW) {
                        display_addr = addr.address;
                    } else {
                        int addrTruncLen = (addrTable.columns.count("address") && addrTable.columns.at("address").truncate > 0)
                            ? addrTable.columns.at("address").truncate : 32;
                        display_addr = truncateAddress(addr.address, addrTruncLen);
                    }
                    dl->AddText(capFont, capFont->LegacySize,
                                ImVec2(labelX, cy + body2->LegacySize + Layout::spacingXs()),
                                OnSurfaceMedium(), display_addr.c_str());
                }

                // ---- Balance (right-aligned, first line) ----
                {
                    char balBuf[32];
                    snprintf(balBuf, sizeof(balBuf), "%.8f", addr.balance);
                    ImVec2 balSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, balBuf);
                    float balX = contentRight - balSz.x;
                    ImU32 balCol = addr.balance > 0.0
                        ? (row.isZ ? greenCol : OnSurface())
                        : OnSurfaceDisabled();
                    if (row.hidden) balCol = OnSurfaceDisabled();
                    DrawTextShadow(dl, body2, body2->LegacySize, ImVec2(balX, cy), balCol, balBuf);
                }

                // ---- USD value (second line, right-aligned) ----
                {
                    std::string usdText = FormatAddressUsdValue(addr.balance, state.market.price_usd);
                    if (!usdText.empty()) {
                        ImVec2 usdSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, usdText.c_str());
                        dl->AddText(capFont, capFont->LegacySize,
                                    ImVec2(contentRight - usdSz.x,
                                           cy + body2->LegacySize + Layout::spacingXs()),
                                    OnSurfaceDisabled(), usdText.c_str());
                    }
                }

                // ---- Copy feedback flash ----
                if (s_copiedRow == row_idx) {
                    float elapsed = (float)ImGui::GetTime() - s_copiedTime;
                    if (elapsed < 1.0f) {
                        float alpha = 1.0f - elapsed;
                        ImFont* capF = Type().caption();
                        const char* copiedTxt = TR("copied");
                        ImVec2 sz = capF->CalcTextSizeA(capF->LegacySize, 1000.0f, 0.0f, copiedTxt);
                        ImVec4 fc = ImGui::ColorConvertU32ToFloat4(Success());
                        fc.w = alpha;
                        float tx = rowPos.x + (innerW - sz.x) * 0.5f;
                        float ty = rowPos.y + (rowH - sz.y) * 0.5f;
                        dl->AddText(capF, capF->LegacySize, ImVec2(tx, ty),
                            ImGui::ColorConvertFloat4ToU32(fc), copiedTxt);
                    } else {
                        s_copiedRow = -1;
                    }
                }

                // ---- Click: begin drag tracking (copy deferred to mouse-up) ----
                if (hovered && mouseClicked && !btnClicked) {
                    s_dragIdx = row_idx;
                    s_dragOffsetY = mousePos.y - rowPos.y;
                    s_dragStartY = mousePos.y;
                    s_dragActive = false;
                }

                // Activate drag after a small threshold
                if (s_dragIdx == row_idx && !s_dragActive && mouseDown) {
                    if (std::abs(mousePos.y - s_dragStartY) > 6.0f * dp) {
                        s_dragActive = true;
                        s_dropTargetIdx = -1;
                    }
                }
            } // end !isDragged

            // Advance cursor for interaction area
            ImGui::PushID(row_idx);
            ImGui::SetCursorScreenPos(ImVec2(rowPos.x, rowY[row_idx]));
            ImGui::InvisibleButton("##addr", ImVec2(innerW, rowH));
            if (ImGui::IsItemHovered() && !s_dragActive) {
                ImGui::SetTooltip("%s", addr.address.c_str());
            }

            // Context menu
            const auto& acrTheme = GetCurrentAcrylicTheme();
            if (effects::ImGuiAcrylic::BeginAcrylicContextItem("AddressContext", 0, acrTheme.menu)) {
                if (ImGui::MenuItem(TR("copy_address"))) {
                    ImGui::SetClipboardText(addr.address.c_str());
                    s_copiedRow = row_idx;
                    s_copiedTime = (float)ImGui::GetTime();
                }
                if (ImGui::MenuItem(TR("send_from_this_address"))) {
                    SetSendFromAddress(addr.address);
                    app->setCurrentPage(NavPage::Send);
                }
                ImGui::Separator();
                if (ImGui::MenuItem(TR("set_label"))) {
                    AddressLabelDialog::show(app, addr.address, row.isZ);
                }
                if (ImGui::MenuItem(row.mining ? TR("unmark_mining_address") : TR("mark_mining_address"))) {
                    app->setMiningAddress(addr.address, !row.mining);
                }
                if (ImGui::MenuItem(TR("export_private_key")))
                    KeyExportDialog::show(addr.address, KeyExportDialog::KeyType::Private);
                if (row.isZ) {
                    if (ImGui::MenuItem(TR("export_viewing_key")))
                        KeyExportDialog::show(addr.address, KeyExportDialog::KeyType::Viewing);
                }
                if (ImGui::MenuItem(TR("show_qr_code")))
                    QRPopupDialog::show(addr.address, row.isZ ? TR("z_address") : TR("t_address"));
                ImGui::Separator();
                if (row.hidden) {
                    if (ImGui::MenuItem(TR("restore_address")))
                        app->unhideAddress(addr.address);
                } else if (addr.balance < 1e-9) {
                    if (ImGui::MenuItem(TR("hide_address")))
                        app->hideAddress(addr.address);
                }
                if (row.favorite) {
                    if (ImGui::MenuItem(TR("remove_favorite")))
                        app->unfavoriteAddress(addr.address);
                } else {
                    if (ImGui::MenuItem(TR("favorite_address")))
                        app->favoriteAddress(addr.address);
                }
                effects::ImGuiAcrylic::EndAcrylicPopup();
            }
            ImGui::PopID();

            // Separator between rows
            if (row_idx < (int)rows.size() - 1 && selected_row != row_idx) {
                float sepY = rowY[row_idx] + rowH;
                dl->AddLine(
                    ImVec2(rowPos.x + rowPadLeft + rowIconSz * 2.0f, sepY),
                    ImVec2(rowPos.x + innerW - Layout::spacingLg(), sepY),
                    dividerCol);
            }
        }

        // Advance cursor past all rows
        ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x,
            cursorStartY + (float)rows.size() * rowH));

        // ---- Keyboard navigation ----
        if (!ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl && !s_dragActive) {
            bool nav = false;
            if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_K)) {
                if (selected_row > 0) { selected_row--; nav = true; }
                else if (selected_row < 0 && !rows.empty()) { selected_row = 0; nav = true; }
            }
            if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsKeyPressed(ImGuiKey_J)) {
                if (selected_row < (int)rows.size() - 1) { selected_row++; nav = true; }
                else if (selected_row < 0 && !rows.empty()) { selected_row = 0; nav = true; }
            }
            if (ImGui::IsKeyPressed(ImGuiKey_Enter) && selected_row >= 0 && selected_row < (int)rows.size()) {
                ImGui::SetClipboardText(rows[selected_row].info->address.c_str());
                s_copiedRow = selected_row;
                s_copiedTime = (float)ImGui::GetTime();
            }
            if (ImGui::IsKeyPressed(ImGuiKey_F2) && selected_row >= 0 && selected_row < (int)rows.size()) {
                const auto& r = rows[selected_row];
                AddressLabelDialog::show(app, r.info->address, r.isZ);
            }
            (void)nav;
        }
    }

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

    // Scroll fade
    {
        float fadeZone = std::min(
            (Type().body2()->LegacySize + Type().caption()->LegacySize +
             Layout::spacingLg() + Layout::spacingMd()) * 1.2f,
            addrListH * 0.18f);
        ApplyScrollEdgeMask(dlPanel, addrParentVtx, addrChildDL, addrChildVtx,
                            listPanelMin.y, listPanelMax.y, fadeZone, addrScrollY, addrScrollMaxY);
    }

    // Address count
    {
        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
        char countBuf[128];
        int totalAddrs = (int)(state.z_addresses.size() + state.t_addresses.size());
        snprintf(countBuf, sizeof(countBuf), TR("showing_x_of_y"),
                 (int)rows.size(), totalAddrs);
        Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), countBuf);
    }
}

// Render the shared recent transactions section (used by all layouts)
static void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float vs) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const float dp = Layout::dpiScale();
    const auto actionBtn = S.button("tabs.balance", "action-button");
    const float kRecentTxRowHeight = S.drawElement("tabs.balance", "recent-tx-row-height").sizeOr(22.0f);
    const auto& state = app->state();

    ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
    Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "RECENT TRANSACTIONS");
    ImGui::SameLine();
    if (TactileSmallButton("View All", S.resolveFont(actionBtn.font.empty() ? "button" : actionBtn.font))) {
        app->setCurrentPage(NavPage::History);
    }
    ImGui::Spacing();

    float scaledRowH = std::max(S.drawElement("tabs.balance", "recent-tx-row-min-height").size, kRecentTxRowHeight * vs);
    float availableListH = ImGui::GetContentRegionAvail().y;
    float listH = std::max({scaledRowH, recentH, availableListH});
    ImGui::BeginChild("##BalanceSharedRecentRows", ImVec2(0, listH), false,
        ImGuiWindowFlags_NoBackground);

    const auto& txs = state.transactions;
    int count = (int)txs.size();

    if (count == 0) {
        Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), "No transactions yet");
    } else {
        ImDrawList* dl = ImGui::GetWindowDrawList();
        ImFont* capFont = Type().caption();
        float rowH = std::max(18.0f * dp, kRecentTxRowHeight * vs);
        float iconSz = std::max(S.drawElement("tabs.balance", "recent-tx-icon-min-size").size,
                                 S.drawElement("tabs.balance", "recent-tx-icon-size").size * hs);

        for (int i = 0; i < count; i++) {
            const auto& tx = txs[i];
            ImVec2 rowPos = ImGui::GetCursorScreenPos();
            float rowY = rowPos.y + rowH * 0.5f;
            auto display = buildRecentTxDisplay(tx, (int)S.drawElement("tabs.balance", "recent-tx-addr-trunc").sizeOr(20.0f));
            DrawTxIcon(dl, tx.type, rowPos.x + Layout::spacingMd(), rowY, iconSz, recentTxIconColor(tx.type));

            float tx_x = rowPos.x + Layout::spacingMd() + iconSz * 2.0f + Layout::spacingSm();
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(tx_x, rowPos.y + 2 * dp), OnSurfaceMedium(), display.typeText.c_str());

            float addrX = tx_x + S.drawElement("tabs.balance", "recent-tx-addr-offset").sizeOr(65.0f);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(addrX, rowPos.y + 2 * dp), OnSurfaceDisabled(), display.addressText.c_str());

            ImVec2 amtSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, display.amountText.c_str());
            float rightEdge = rowPos.x + ImGui::GetContentRegionAvail().x;
            float amtX = rightEdge - amtSz.x - std::max(
                S.drawElement("tabs.balance", "amount-right-min-margin").size,
                S.drawElement("tabs.balance", "amount-right-margin").size * hs);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(amtX, rowPos.y + 2 * dp),
                        recentTxAmountColor(tx.type), display.amountText.c_str());

            ImVec2 agoSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, display.timeText.c_str());
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(rightEdge - agoSz.x - S.drawElement("tabs.balance", "recent-tx-time-margin").sizeOr(4.0f), rowPos.y + 2 * dp),
                        OnSurfaceDisabled(), display.timeText.c_str());

            float rowW = ImGui::GetContentRegionAvail().x;
            ImVec2 rowEnd(rowPos.x + rowW, rowPos.y + rowH);
            if (material::IsRectHovered(rowPos, rowEnd)) {
                dl->AddRectFilled(rowPos, rowEnd, IM_COL32(255, 255, 255, 15), S.drawElement("tabs.balance", "row-hover-rounding").sizeOr(4.0f));
                ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                if (ImGui::IsMouseClicked(0))
                    app->setCurrentPage(NavPage::History);
            }
            ImGui::Dummy(ImVec2(0, rowH));
        }
    }
    ImGui::EndChild();
}

// Render sync progress bar (used by multiple layouts)
static void RenderSyncBar(App* app, ImDrawList* dl, float vs) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto syncBar = S.drawElement("tabs.balance", "sync-bar");
    const auto& state = app->state();
    const float dp = Layout::dpiScale();

    if (state.sync.syncing && state.sync.headers > 0) {
        float prog = static_cast<float>(state.sync.verification_progress);
        if (prog > 1.0f) prog = 1.0f;
        float barH = (syncBar.height >= 0) ? syncBar.height : 3.0f;
        float barW = ImGui::GetContentRegionAvail().x;
        ImVec2 barPos = ImGui::GetCursorScreenPos();
        dl->AddRectFilled(barPos,
            ImVec2(barPos.x + barW, barPos.y + barH),
            IM_COL32(255, 255, 255, 15), 1.0f * dp);
        dl->AddRectFilled(barPos,
            ImVec2(barPos.x + barW * prog, barPos.y + barH),
            WithAlpha(Warning(), 200), 1.0f * dp);
        ImGui::Dummy(ImVec2(barW, barH));
    }
}

// ============================================================================
// Layout 1: Donut Chart
// ============================================================================
static void RenderBalanceDonut(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    float glassRound = Layout::glassRounding();
    float cGap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    // --- Hero: total balance ---
    float donutTopMargin = S.drawElement("tabs.balance.donut", "top-margin").size;
    if (donutTopMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, donutTopMargin));
    else
        ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance.donut", "hero-pad-ratio").sizeOr(8.0f) * vs));
    {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TOTAL BALANCE");
        ImGui::Dummy(ImVec2(0, 2 * dp));
        snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
        ImFont* heroFont = Type().h2();
        ImVec2 pos = ImGui::GetCursorScreenPos();
        DrawTextShadow(dl, heroFont, heroFont->LegacySize, pos, OnSurface(), buf);
        ImVec2 heroSize = heroFont->CalcTextSizeA(heroFont->LegacySize, 10000.0f, 0.0f, buf);
        ImGui::Dummy(heroSize);
        ImGui::SameLine();
        ImFont* capFont = Type().caption();
        float tickerY = pos.y + heroSize.y - capFont->LegacySize;
        dl->AddText(capFont, capFont->LegacySize,
                    ImVec2(ImGui::GetCursorScreenPos().x, tickerY),
                    OnSurfaceMedium(), DRAGONX_TICKER);
        ImGui::NewLine();

        // USD value
        double usd_value = state.getBalanceUSD();
        if (usd_value > 0.0) snprintf(buf, sizeof(buf), "$%.2f USD", usd_value);
        else snprintf(buf, sizeof(buf), "$-.-- USD");
        Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
    }
    RenderSyncBar(app, dl, vs);
    ImGui::Dummy(ImVec2(0, cGap));

    // --- Donut + legend panel ---
    {
        ImFont* donutOv = Type().overline();
        ImFont* donutCap = Type().caption();
        // Font-content floor: legend needs overline + 3 caption lines + spacing
        float donutFontFloor = Layout::spacingLg() * 2
            + donutOv->LegacySize + Layout::spacingMd()
            + donutCap->LegacySize * 3 + Layout::spacingSm() * 3;
        float donutCardH = S.drawElement("tabs.balance.donut", "card-height").size;
        float panelH;
        if (donutCardH >= 0.0f) {
            panelH = std::max(donutCardH * dp, donutFontFloor);
        } else {
            panelH = std::max({donutFontFloor,
                S.drawElement("tabs.balance.donut", "panel-min-height").sizeOr(80.0f) * dp,
                contentAvail.y * S.drawElement("tabs.balance.donut", "panel-height-ratio").sizeOr(0.20f)});
        }
        ImVec2 panelMin = ImGui::GetCursorScreenPos();
        ImVec2 panelMax(panelMin.x + availW, panelMin.y + panelH);
        GlassPanelSpec spec;
        spec.rounding = glassRound;
        DrawGlassPanel(dl, panelMin, panelMax, spec);

        float donutPadOverride = S.drawElement("tabs.balance.donut", "card-padding").size;
        float donutPad = (donutPadOverride >= 0.0f) ? donutPadOverride : Layout::spacingLg();

        // Donut ring
        float cx = panelMin.x + panelH * 0.5f + donutPad;
        float cy = panelMin.y + panelH * 0.5f;
        float radius = std::min(
            panelH * S.drawElement("tabs.balance.donut", "outer-radius-ratio").sizeOr(0.40f),
            availW * S.drawElement("tabs.balance.donut", "max-radius-ratio").sizeOr(0.12f));
        float innerRadius = radius * S.drawElement("tabs.balance.donut", "inner-radius-ratio").sizeOr(0.6f);

        float total = (float)s_dispTotal;
        float shielded = (float)s_dispShielded;
        float ratio = (total > 1e-9f) ? shielded / total : 0.5f;

        // Shielded arc (green)
        float startAngle = -IM_PI * 0.5f; // top
        float shieldEnd = startAngle + 2.0f * IM_PI * ratio;
        if (ratio > 0.01f) {
            dl->PathClear();
            dl->PathArcTo(ImVec2(cx, cy), radius, startAngle, shieldEnd, 32);
            dl->PathArcTo(ImVec2(cx, cy), innerRadius, shieldEnd, startAngle, 32);
            dl->PathFillConvex(WithAlpha(Success(), 180));
        }
        // Transparent arc (gold)
        if (ratio < 0.99f) {
            dl->PathClear();
            dl->PathArcTo(ImVec2(cx, cy), radius, shieldEnd, startAngle + 2.0f * IM_PI, 32);
            dl->PathArcTo(ImVec2(cx, cy), innerRadius, startAngle + 2.0f * IM_PI, shieldEnd, 32);
            dl->PathFillConvex(WithAlpha(Warning(), 180));
        }

        // Center text: privacy %
        float privPct = ratio * 100.0f;
        snprintf(buf, sizeof(buf), "%.0f%%", privPct);
        ImFont* sub1 = Type().subtitle1();
        ImVec2 pctSz = sub1->CalcTextSizeA(sub1->LegacySize, 1000, 0, buf);
        dl->AddText(sub1, sub1->LegacySize,
                    ImVec2(cx - pctSz.x * 0.5f, cy - pctSz.y * 0.5f),
                    OnSurface(), buf);

        // Legend (right side)
        float legendX = panelMin.x + panelH + donutPad * 2;
        float legendY = panelMin.y + donutPad;
        ImFont* capFont = Type().caption();
        ImFont* body2 = Type().body2();

        float legendDotR = S.drawElement("tabs.balance.donut", "legend-dot-radius").sizeOr(4.0f);
        float legendXOff = S.drawElement("tabs.balance.donut", "legend-x-offset").sizeOr(14.0f);
        float legendLineGap = S.drawElement("tabs.balance.donut", "legend-line-gap").sizeOr(6.0f);
        float legendSectionGap = S.drawElement("tabs.balance.donut", "legend-section-gap").sizeOr(10.0f);

        // Shielded legend
        dl->AddCircleFilled(ImVec2(legendX + 5 * dp, legendY + capFont->LegacySize * 0.5f), legendDotR, Success());
        snprintf(buf, sizeof(buf), "Shielded  %.8f", s_dispShielded);
        dl->AddText(capFont, capFont->LegacySize, ImVec2(legendX + legendXOff, legendY), Success(), buf);
        legendY += capFont->LegacySize + legendLineGap;

        // Transparent legend
        dl->AddCircleFilled(ImVec2(legendX + 5 * dp, legendY + capFont->LegacySize * 0.5f), legendDotR, Warning());
        snprintf(buf, sizeof(buf), "Transparent  %.8f", s_dispTransparent);
        dl->AddText(capFont, capFont->LegacySize, ImVec2(legendX + legendXOff, legendY), Warning(), buf);
        legendY += capFont->LegacySize + legendSectionGap;

        // Market price
        const auto& market = state.market;
        if (market.price_usd > 0) {
            if (market.price_usd >= 0.01)
                snprintf(buf, sizeof(buf), "Market: $%.4f", market.price_usd);
            else
                snprintf(buf, sizeof(buf), "Market: $%.8f", market.price_usd);
            dl->AddText(capFont, capFont->LegacySize, ImVec2(legendX + legendXOff, legendY),
                        OnSurfaceMedium(), buf);
            legendY += capFont->LegacySize + 4 * dp;

            bool pos = market.change_24h >= 0;
            snprintf(buf, sizeof(buf), "%s%.1f%% 24h", pos ? "+" : "", market.change_24h);
            dl->AddText(capFont, capFont->LegacySize, ImVec2(legendX + legendXOff, legendY),
                        pos ? Success() : Error(), buf);
        }

        ImGui::Dummy(ImVec2(availW, panelH));
    }

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

    // --- Shared address list + recent tx ---
    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float donutAddrOverride = S.drawElement("tabs.balance.donut", "address-table-height").size;
    float addrH = (donutAddrOverride >= 0.0f) ? donutAddrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

// ============================================================================
// Layout 2: Consolidated Card
// ============================================================================
static void RenderBalanceConsolidated(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    float glassRound = Layout::glassRounding();
    float cGap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    // Single consolidated card
    float consTopMargin = S.drawElement("tabs.balance.consolidated", "top-margin").size;
    if (consTopMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, consTopMargin));

    ImFont* heroFont = Type().h2();
    ImFont* sub1 = Type().subtitle1();
    ImFont* capFont = Type().caption();
    ImFont* ovFont = Type().overline();
    float consPadOverride = S.drawElement("tabs.balance.consolidated", "card-padding").size;
    float pad = (consPadOverride >= 0.0f) ? consPadOverride : Layout::spacingLg();
    // Font-content floor: pad + overline + hero + caption + subtitle + pad
    float consFontFloor = pad + ovFont->LegacySize + Layout::spacingSm()
        + heroFont->LegacySize + Layout::spacingSm()
        + capFont->LegacySize + Layout::spacingSm()
        + sub1->LegacySize + pad;
    float consCardH = S.drawElement("tabs.balance.consolidated", "card-height").size;
    float cardH;
    if (consCardH >= 0.0f) {
        cardH = std::max(consCardH * dp, consFontFloor);
    } else {
        cardH = std::max({consFontFloor,
            S.drawElement("tabs.balance.consolidated", "card-min-height").sizeOr(90.0f) * dp,
            contentAvail.y * S.drawElement("tabs.balance.consolidated", "card-height-ratio").sizeOr(0.22f)});
    }
    ImVec2 cardMin = ImGui::GetCursorScreenPos();
    ImVec2 cardMax(cardMin.x + availW, cardMin.y + cardH);
    GlassPanelSpec spec;
    spec.rounding = glassRound;
    DrawGlassPanel(dl, cardMin, cardMax, spec);

    float cx = cardMin.x + pad;
    float cy = cardMin.y + pad;

    // Coin logo
    ImTextureID logoTex = app->getCoinLogoTexture();

    float logoSz = heroFont->LegacySize + capFont->LegacySize + 4.0f * dp;
    if (logoTex != 0) {
        dl->AddImage(logoTex,
            ImVec2(cx, cy), ImVec2(cx + logoSz, cy + logoSz),
            ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 255));
        cx += logoSz + Layout::spacingMd();
    }

    // Total balance
    snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
    DrawTextShadow(dl, heroFont, heroFont->LegacySize, ImVec2(cx, cy), OnSurface(), buf);
    ImVec2 heroSz = heroFont->CalcTextSizeA(heroFont->LegacySize, 10000, 0, buf);
    dl->AddText(capFont, capFont->LegacySize,
                ImVec2(cx + heroSz.x + 4 * dp, cy + heroSz.y - capFont->LegacySize),
                OnSurfaceMedium(), DRAGONX_TICKER);
    cy += heroSz.y + 2 * dp;

    // USD value
    double usd_value = state.getBalanceUSD();
    if (usd_value > 0.0) snprintf(buf, sizeof(buf), "$%.2f USD", usd_value);
    else snprintf(buf, sizeof(buf), "$-.-- USD");
    dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceDisabled(), buf);

    // Market badge (top-right)
    {
        const auto& market = state.market;
        if (market.price_usd > 0) {
            float badgeX = cardMax.x - pad;
            float badgeY = cardMin.y + pad;
            if (market.price_usd >= 0.01)
                snprintf(buf, sizeof(buf), "$%.4f", market.price_usd);
            else
                snprintf(buf, sizeof(buf), "$%.8f", market.price_usd);
            ImVec2 pSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(badgeX - pSz.x, badgeY), OnSurfaceMedium(), buf);
            badgeY += capFont->LegacySize + 2 * dp;

            bool pos = market.change_24h >= 0;
            snprintf(buf, sizeof(buf), "%s%.1f%%", pos ? "+" : "", market.change_24h);
            ImVec2 chgSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(badgeX - chgSz.x, badgeY),
                        pos ? Success() : Error(), buf);
        }
    }

    // Divider
    float divY = cardMin.y + cardH * S.drawElement("tabs.balance.consolidated", "divider-y-ratio").sizeOr(0.55f);
    dl->AddLine(ImVec2(cardMin.x + pad, divY), ImVec2(cardMax.x - pad, divY),
                IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance.consolidated", "divider-alpha").sizeOr(20.0f)),
                S.drawElement("tabs.balance.consolidated", "divider-thickness").sizeOr(1.0f));

    // Bottom half: proportion bars
    float barY = divY + Layout::spacingSm();
    float barH = std::max(
        S.drawElement("tabs.balance.consolidated", "bar-min-height").sizeOr(6.0f),
        S.drawElement("tabs.balance.consolidated", "bar-base-height").sizeOr(10.0f) * vs);
    float halfW = (availW - pad * 3) * 0.5f;
    float total = (float)s_dispTotal;
    float shieldRatio = (total > 1e-9f) ? (float)(s_dispShielded / total) : 0.5f;
    float transRatio = 1.0f - shieldRatio;

    // Shielded bar
    float shieldX = cardMin.x + pad;
    dl->AddText(ovFont, ovFont->LegacySize, ImVec2(shieldX, barY), Success(), "SHIELDED");
    barY += ovFont->LegacySize + 4 * dp;
    dl->AddRectFilled(ImVec2(shieldX, barY), ImVec2(shieldX + halfW, barY + barH),
                      IM_COL32(255, 255, 255, 15), barH * 0.5f);
    dl->AddRectFilled(ImVec2(shieldX, barY), ImVec2(shieldX + halfW * shieldRatio, barY + barH),
                      WithAlpha(Success(), 180), barH * 0.5f);
    barY += barH + 2 * dp;
    snprintf(buf, sizeof(buf), "%.8f (%.0f%%)", s_dispShielded, shieldRatio * 100.0f);
    dl->AddText(capFont, capFont->LegacySize, ImVec2(shieldX, barY), OnSurfaceMedium(), buf);

    // Transparent bar
    float transX = cardMin.x + pad * 2 + halfW;
    barY = divY + Layout::spacingSm();
    dl->AddText(ovFont, ovFont->LegacySize, ImVec2(transX, barY), Warning(), "TRANSPARENT");
    barY += ovFont->LegacySize + 4 * dp;
    dl->AddRectFilled(ImVec2(transX, barY), ImVec2(transX + halfW, barY + barH),
                      IM_COL32(255, 255, 255, 15), barH * 0.5f);
    dl->AddRectFilled(ImVec2(transX, barY), ImVec2(transX + halfW * transRatio, barY + barH),
                      WithAlpha(Warning(), 180), barH * 0.5f);
    barY += barH + 2 * dp;
    snprintf(buf, sizeof(buf), "%.8f (%.0f%%)", s_dispTransparent, transRatio * 100.0f);
    dl->AddText(capFont, capFont->LegacySize, ImVec2(transX, barY), OnSurfaceMedium(), buf);

    // Sync bar at card bottom — clipped to rounded corners
    if (state.sync.syncing && state.sync.headers > 0) {
        const auto syncBar = S.drawElement("tabs.balance", "sync-bar");
        float syncBarH = (syncBar.height >= 0) ? syncBar.height : 3.0f;
        float prog = static_cast<float>(state.sync.verification_progress);
        if (prog > 1.0f) prog = 1.0f;
        float syncBarTop = cardMax.y - syncBarH;
        // Clip to the bottom strip so the full-card rounded rect
        // curves exactly match the card's own rounded corners
        dl->PushClipRect(ImVec2(cardMin.x, syncBarTop), cardMax, true);
        // Background track
        dl->AddRectFilled(cardMin, cardMax,
            IM_COL32(255, 255, 255, 15), glassRound);
        // Progress fill — additional horizontal clip
        float progRight = cardMin.x + (cardMax.x - cardMin.x) * prog;
        dl->PushClipRect(ImVec2(cardMin.x, syncBarTop), ImVec2(progRight, cardMax.y), true);
        dl->AddRectFilled(cardMin, cardMax,
            WithAlpha(Warning(), 200), glassRound);
        dl->PopClipRect();
        dl->PopClipRect();
    }
    ImGui::Dummy(ImVec2(availW, cardH));
    ImGui::Dummy(ImVec2(0, cGap));

    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float consAddrOverride = S.drawElement("tabs.balance.consolidated", "address-table-height").size;
    float addrH = (consAddrOverride >= 0.0f) ? consAddrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

// ============================================================================
// Layout 3: Dashboard Tiles
// ============================================================================
static void RenderBalanceDashboard(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    auto tier = Layout::currentTier(availW, contentAvail.y);
    float glassRound = Layout::glassRounding();
    float cGap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    // Compact hero line
    float dashTopMargin = S.drawElement("tabs.balance.dashboard", "top-margin").size;
    if (dashTopMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, dashTopMargin));
    float dashHeroH = S.drawElement("tabs.balance.dashboard", "hero-height").size;
    RenderCompactHero(app, dl, availW, hs, vs, dashHeroH);
    RenderSyncBar(app, dl, vs);
    ImGui::Dummy(ImVec2(0, cGap));

    // 4-tile grid
    int numCols = (tier == Layout::LayoutTier::Compact && availW < S.drawElement("tabs.balance.dashboard", "compact-cutoff").sizeOr(500.0f) * Layout::dpiScale())
        ? (int)S.drawElement("tabs.balance.dashboard", "tile-compact-cols").sizeOr(2.0f)
        : (int)S.drawElement("tabs.balance.dashboard", "tile-num-cols").sizeOr(4.0f);
    int numRows = (numCols == 2) ? 2 : 1;
    float tileW = (availW - (numCols - 1) * cGap) / numCols;
    ImFont* ovFont = Type().overline();
    ImFont* sub1 = Type().subtitle1();
    ImFont* capFont = Type().caption();
    // Font-content floor: pad + overline + subtitle1 + caption + pad
    float dashFontFloor = Layout::spacingLg()
        + ovFont->LegacySize + Layout::spacingSm()
        + sub1->LegacySize + Layout::spacingSm()
        + capFont->LegacySize
        + Layout::spacingLg();
    float dashCardH = S.drawElement("tabs.balance.dashboard", "card-height").size;
    float tileH;
    if (dashCardH >= 0.0f) {
        tileH = std::max(dashCardH * dp, dashFontFloor);
    } else {
        tileH = std::max({dashFontFloor,
            S.drawElement("tabs.balance.dashboard", "tile-min-height").sizeOr(70.0f) * dp,
            contentAvail.y * S.drawElement("tabs.balance.dashboard", "tile-height-ratio").sizeOr(0.16f) / numRows});
    }
    ImVec2 origin = ImGui::GetCursorScreenPos();

    GlassPanelSpec tileSpec;
    tileSpec.rounding = glassRound;

    struct TileInfo {
        const char* label;
        const char* value;
        ImU32 accent;
        const char* icon;
        NavPage nav;
        bool isAction;
    };

    snprintf(buf, sizeof(buf), "%.8f", s_dispShielded);
    static char shBuf[64], trBuf[64];
    snprintf(shBuf, sizeof(shBuf), "%.8f", s_dispShielded);
    snprintf(trBuf, sizeof(trBuf), "%.8f", s_dispTransparent);

    TileInfo tiles[4] = {
        {"SHIELDED", shBuf, S.resolveColor("var(--accent-shielded)", Success()), ICON_MD_SHIELD, NavPage::Receive, false},
        {"TRANSPARENT", trBuf, S.resolveColor("var(--accent-transparent)", Warning()), ICON_MD_CIRCLE, NavPage::Receive, false},
        {"QUICK SEND", "Send", S.resolveColor("var(--accent-action)", Primary()), ICON_MD_CALL_MADE, NavPage::Send, true},
        {"QUICK RECEIVE", "Receive", S.resolveColor("var(--accent-action)", Primary()), ICON_MD_CALL_RECEIVED, NavPage::Receive, true},
    };

    for (int i = 0; i < 4; i++) {
        int col = (numCols == 4) ? i : (i % 2);
        int row = (numCols == 4) ? 0 : (i / 2);
        float xOff = col * (tileW + cGap);
        float yOff = row * (tileH + cGap);

        ImVec2 tMin(origin.x + xOff, origin.y + yOff);
        ImVec2 tMax(tMin.x + tileW, tMin.y + tileH);
        DrawGlassPanel(dl, tMin, tMax, tileSpec);

        // Accent stripe — clipped to tile rounded corners
        {
            float aw = S.drawElement("tabs.balance", "accent-width").sizeOr(4.0f);
            dl->PushClipRect(tMin, ImVec2(tMin.x + aw, tMax.y), true);
            dl->AddRectFilled(tMin, tMax, tiles[i].accent, tileSpec.rounding,
                              ImDrawFlags_RoundCornersLeft);
            dl->PopClipRect();
        }

        float dashPadOverride = S.drawElement("tabs.balance.dashboard", "card-padding").size;
        float tilePad = (dashPadOverride >= 0.0f) ? dashPadOverride : Layout::spacingLg();
        float tilePadV = (dashPadOverride >= 0.0f) ? dashPadOverride : Layout::spacingSm();

        float px = tMin.x + tilePad;
        float py = tMin.y + tilePadV;

        // Icon
        ImFont* iconFont = Type().iconSmall();
        ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, tiles[i].icon);
        dl->AddText(iconFont, iconFont->LegacySize,
                    ImVec2(px, py), tiles[i].accent, tiles[i].icon);
        px += iSz.x + Layout::spacingSm();

        // Label
        dl->AddText(ovFont, ovFont->LegacySize, ImVec2(px, py), OnSurfaceMedium(), tiles[i].label);
        py += ovFont->LegacySize + 4 * dp;

        // Value
        if (!tiles[i].isAction) {
            dl->AddText(capFont, capFont->LegacySize, ImVec2(tMin.x + tilePad, py),
                        tiles[i].accent, tiles[i].value);
        } else {
            dl->AddText(capFont, capFont->LegacySize, ImVec2(tMin.x + tilePad, py),
                        OnSurfaceMedium(), "Click to open");
        }

        // Click
        if (material::IsRectHovered(tMin, tMax)) {
            dl->AddRect(tMin, tMax, IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance", "hover-glow-alpha").sizeOr(40.0f)),
                        tileSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f));
            ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
            if (ImGui::IsMouseClicked(0))
                app->setCurrentPage(tiles[i].nav);
        }
    }

    float totalTileH = numRows * tileH + (numRows - 1) * cGap;
    ImGui::Dummy(ImVec2(availW, totalTileH));
    ImGui::Dummy(ImVec2(0, cGap));

    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float dashAddrOverride = S.drawElement("tabs.balance.dashboard", "address-table-height").size;
    float addrH = (dashAddrOverride >= 0.0f) ? dashAddrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

// ============================================================================
// Layout 4: Vertical Stack
// ============================================================================
static void RenderBalanceVerticalStack(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    float glassRound = Layout::glassRounding();
    float cGap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    float vstackTopMargin = S.drawElement("tabs.balance.vertical-stack", "top-margin").size;
    if (vstackTopMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, vstackTopMargin));

    ImFont* capFont = Type().caption();
    ImFont* body2 = Type().body2();
    ImFont* sub1 = Type().subtitle1();
    // Font-content floor per row: icon + label + value must fit
    float vstackRowFontFloor = std::max(body2->LegacySize, capFont->LegacySize)
        + Layout::spacingSm() * 2;
    float rowGap = S.drawElement("tabs.balance.vertical-stack", "row-gap").sizeOr(2.0f);
    float vstackFontFloor = vstackRowFontFloor * 4 + rowGap * 3;
    float vstackCardH = S.drawElement("tabs.balance.vertical-stack", "card-height").size;
    float stackH;
    if (vstackCardH >= 0.0f) {
        stackH = std::max(vstackCardH * dp, vstackFontFloor);
    } else {
        stackH = std::max({vstackFontFloor,
            S.drawElement("tabs.balance.vertical-stack", "stack-min-height").sizeOr(80.0f) * dp,
            contentAvail.y * S.drawElement("tabs.balance.vertical-stack", "stack-height-ratio").sizeOr(0.16f)});
    }
    float rowH = (stackH - 3 * rowGap) / 4.0f;
    float rowMinH = std::max(
        S.drawElement("tabs.balance.vertical-stack", "row-min-height").sizeOr(20.0f) * dp,
        vstackRowFontFloor);
    if (rowH < rowMinH) rowH = rowMinH;

    float total = (float)s_dispTotal;
    float shieldRatio = (total > 1e-9f) ? (float)(s_dispShielded / total) : 0.5f;
    float transRatio = 1.0f - shieldRatio;

    struct RowInfo {
        const char* label;
        const char* icon;
        ImU32 accent;
        double amount;
        float ratio;
    };

    RowInfo rowInfos[4] = {
        {"Total Balance", ICON_MD_ACCOUNT_BALANCE_WALLET, S.resolveColor("var(--accent-total)", OnSurface()), s_dispTotal, 1.0f},
        {"Shielded", ICON_MD_SHIELD, S.resolveColor("var(--accent-shielded)", Success()), s_dispShielded, shieldRatio},
        {"Transparent", ICON_MD_CIRCLE, S.resolveColor("var(--accent-transparent)", Warning()), s_dispTransparent, transRatio},
        {"Market", ICON_MD_TRENDING_UP, S.resolveColor("var(--accent-action)", Primary()), state.market.price_usd, 0.0f},
    };

    for (int i = 0; i < 4; i++) {
        ImVec2 rowPos = ImGui::GetCursorScreenPos();
        ImVec2 rowEnd(rowPos.x + availW, rowPos.y + rowH);

        float vstackPadOverride = S.drawElement("tabs.balance.vertical-stack", "card-padding").size;
        float rowPad = (vstackPadOverride >= 0.0f) ? vstackPadOverride : Layout::spacingLg();

        // Subtle background
        float rowBgAlpha = S.drawElement("tabs.balance.vertical-stack", "row-bg-alpha").sizeOr(8.0f);
        dl->AddRectFilled(rowPos, rowEnd, IM_COL32(255, 255, 255, (int)rowBgAlpha), 4.0f * dp);

        // Left accent — clipped to row rounding
        dl->PushClipRect(rowPos, rowEnd, true);
        dl->AddRectFilled(ImVec2(rowPos.x, rowPos.y),
                          ImVec2(rowPos.x + 3 * dp, rowEnd.y),
                          rowInfos[i].accent);
        dl->PopClipRect();

        float px = rowPos.x + rowPad;
        float cy = rowPos.y + (rowH - capFont->LegacySize) * 0.5f;

        // Icon
        ImFont* iconFont = Type().iconSmall();
        ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, rowInfos[i].icon);
        dl->AddText(iconFont, iconFont->LegacySize,
                    ImVec2(px, rowPos.y + (rowH - iSz.y) * 0.5f),
                    rowInfos[i].accent, rowInfos[i].icon);
        px += iSz.x + Layout::spacingSm();

        // Label
        dl->AddText(capFont, capFont->LegacySize, ImVec2(px, cy),
                    OnSurfaceMedium(), rowInfos[i].label);

        // Amount (right side)
        if (i < 3) {
            snprintf(buf, sizeof(buf), "%.8f %s", rowInfos[i].amount, DRAGONX_TICKER);
        } else {
            if (state.market.price_usd >= 0.01)
                snprintf(buf, sizeof(buf), "$%.4f", state.market.price_usd);
            else if (state.market.price_usd > 0)
                snprintf(buf, sizeof(buf), "$%.8f", state.market.price_usd);
            else
                snprintf(buf, sizeof(buf), "$--.--");
        }
        ImVec2 amtSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
        dl->AddText(capFont, capFont->LegacySize,
                    ImVec2(rowEnd.x - amtSz.x - rowPad, cy),
                    i == 0 ? OnSurface() : rowInfos[i].accent, buf);

        // Proportion bar (for shielded/transparent rows — fills gap between label and amount)
        if (i == 1 || i == 2) {
            ImVec2 labelSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, rowInfos[i].label);
            float barGap = S.drawElement("tabs.balance.vertical-stack", "sparkline-gap").sizeOr(12.0f);
            float barPad = S.drawElement("tabs.balance.vertical-stack", "sparkline-pad").sizeOr(4.0f);
            float barH = std::max(
                S.drawElement("tabs.balance.vertical-stack", "bar-min-height").sizeOr(3.0f),
                rowH * S.drawElement("tabs.balance.vertical-stack", "bar-height-ratio").sizeOr(0.15f));
            float barLeft = px + labelSz.x + barGap;
            float barRight = rowEnd.x - amtSz.x - rowPad - barGap;
            if (barLeft < barRight) {
                float barW = barRight - barLeft;
                float barY = rowPos.y + (rowH - barH) * 0.5f;
                dl->AddRectFilled(ImVec2(barLeft, barY), ImVec2(barRight, barY + barH),
                                  IM_COL32(255, 255, 255, 15), barH * 0.5f);
                dl->AddRectFilled(ImVec2(barLeft, barY),
                                  ImVec2(barLeft + barW * rowInfos[i].ratio, barY + barH),
                                  WithAlpha(rowInfos[i].accent, 180), barH * 0.5f);
            }
        }

        // Market: 24h change + sparkline
        if (i == 3 && state.market.price_usd > 0) {
            bool pos = state.market.change_24h >= 0;
            snprintf(buf, sizeof(buf), "%s%.1f%%", pos ? "+" : "", state.market.change_24h);
            ImVec2 chgSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
            float chgX = rowEnd.x - amtSz.x - Layout::spacingLg() - chgSz.x - Layout::spacingSm();
            dl->AddText(capFont, capFont->LegacySize, ImVec2(chgX, cy),
                        pos ? Success() : Error(), buf);

            // Sparkline in the gap between label and 24h change
            if (state.market.price_history.size() >= 2) {
                ImVec2 labelSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, rowInfos[i].label);
                float sparkGap = S.drawElement("tabs.balance.vertical-stack", "sparkline-gap").sizeOr(12.0f);
                float sparkPad = S.drawElement("tabs.balance.vertical-stack", "sparkline-pad").sizeOr(4.0f);
                float sparkLeft = px + labelSz.x + sparkGap;
                float sparkRight = chgX - sparkGap;
                if (sparkLeft < sparkRight) {
                    ImVec2 spMin(sparkLeft, rowPos.y + sparkPad);
                    ImVec2 spMax(sparkRight, rowEnd.y - sparkPad);
                    ImU32 lineCol = pos
                        ? WithAlpha(Success(), 200)
                        : WithAlpha(Error(), 200);
                    DrawSparkline(dl, spMin, spMax,
                                  state.market.price_history, lineCol);
                }
            }
        }

        ImGui::Dummy(ImVec2(availW, rowH));
        if (i < 3) ImGui::Dummy(ImVec2(0, rowGap));
    }

    RenderSyncBar(app, dl, vs);
    ImGui::Dummy(ImVec2(0, cGap));

    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float vstackAddrOverride = S.drawElement("tabs.balance.vertical-stack", "address-table-height").size;
    float addrH = (vstackAddrOverride >= 0.0f) ? vstackAddrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

// ============================================================================
// Layout 5b: Vertical 2×2 (Total+Market left, Shielded+Transparent right)
// ============================================================================
static void RenderBalanceVertical2x2(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    float glassRound = Layout::glassRounding();
    float cGap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    const char* cfgSec = "tabs.balance.vertical-2x2";

    float topMargin = S.drawElement(cfgSec, "top-margin").size;
    if (topMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, topMargin));

    ImFont* capFont = Type().caption();
    ImFont* iconFont = Type().iconSmall();
    // Font-content floor per row: caption text + vertical padding
    float v2x2RowFontFloor = capFont->LegacySize + Layout::spacingSm() * 2;
    float rowGap = S.drawElement(cfgSec, "row-gap").sizeOr(2.0f);
    float colGap = S.drawElement(cfgSec, "col-gap").sizeOr(8.0f);
    float v2x2FontFloor = v2x2RowFontFloor * 2 + rowGap;
    float cardHOverride = S.drawElement(cfgSec, "card-height").size;
    float stackH;
    if (cardHOverride >= 0.0f) {
        stackH = std::max(cardHOverride * dp, v2x2FontFloor);
    } else {
        stackH = std::max({v2x2FontFloor,
            S.drawElement(cfgSec, "stack-min-height").sizeOr(60.0f) * dp,
            contentAvail.y * S.drawElement(cfgSec, "stack-height-ratio").sizeOr(0.12f)});
    }
    float rowH = (stackH - rowGap) / 2.0f;
    float rowMinH = std::max(
        S.drawElement(cfgSec, "row-min-height").sizeOr(24.0f) * dp,
        v2x2RowFontFloor);
    if (rowH < rowMinH) rowH = rowMinH;

    float colW = (availW - colGap) / 2.0f;

    float padOverride = S.drawElement(cfgSec, "card-padding").size;
    float rowPad = (padOverride >= 0.0f) ? padOverride : Layout::spacingLg();
    float rowBgAlpha = S.drawElement(cfgSec, "row-bg-alpha").sizeOr(8.0f);

    float total = (float)s_dispTotal;
    float shieldRatio = (total > 1e-9f) ? (float)(s_dispShielded / total) : 0.5f;
    float transRatio = 1.0f - shieldRatio;

    // Grid: [row][col] — row 0 top, row 1 bottom; col 0 left, col 1 right
    // Left col:  Total Balance (row 0), Market (row 1)
    // Right col: Shielded (row 0), Transparent (row 1)
    struct CellInfo {
        const char* label;
        const char* icon;
        ImU32 accent;
        double amount;
        float ratio;
        bool isMarket;
        bool hasBar;
    };

    CellInfo cells[2][2] = {
        // Row 0: Total Balance (left), Shielded (right)
        {
            {"Total Balance", ICON_MD_ACCOUNT_BALANCE_WALLET, S.resolveColor("var(--accent-total)", OnSurface()), s_dispTotal, 1.0f, false, false},
            {"Shielded", ICON_MD_SHIELD, S.resolveColor("var(--accent-shielded)", Success()), s_dispShielded, shieldRatio, false, true},
        },
        // Row 1: Market (left), Transparent (right)
        {
            {"Market", ICON_MD_TRENDING_UP, S.resolveColor("var(--accent-action)", Primary()), state.market.price_usd, 0.0f, true, false},
            {"Transparent", ICON_MD_CIRCLE, S.resolveColor("var(--accent-transparent)", Warning()), s_dispTransparent, transRatio, false, true},
        },
    };

    ImVec2 gridOrigin = ImGui::GetCursorScreenPos();

    for (int row = 0; row < 2; row++) {
        for (int col = 0; col < 2; col++) {
            const auto& cell = cells[row][col];

            float cellX = gridOrigin.x + col * (colW + colGap);
            float cellY = gridOrigin.y + row * (rowH + rowGap);
            ImVec2 cellMin(cellX, cellY);
            ImVec2 cellMax(cellX + colW, cellY + rowH);

            // Background
            dl->AddRectFilled(cellMin, cellMax, IM_COL32(255, 255, 255, (int)rowBgAlpha), 4.0f * dp);

            // Left accent — clipped to cell rounding
            dl->PushClipRect(cellMin, cellMax, true);
            dl->AddRectFilled(ImVec2(cellMin.x, cellMin.y),
                              ImVec2(cellMin.x + 3 * dp, cellMax.y),
                              cell.accent);
            dl->PopClipRect();

            float px = cellMin.x + rowPad;
            float cy = cellMin.y + (rowH - capFont->LegacySize) * 0.5f;

            // Icon
            ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, cell.icon);
            dl->AddText(iconFont, iconFont->LegacySize,
                        ImVec2(px, cellMin.y + (rowH - iSz.y) * 0.5f),
                        cell.accent, cell.icon);
            px += iSz.x + Layout::spacingSm();

            // Label
            dl->AddText(capFont, capFont->LegacySize, ImVec2(px, cy),
                        OnSurfaceMedium(), cell.label);

            // Amount (right-aligned)
            if (!cell.isMarket) {
                snprintf(buf, sizeof(buf), "%.8f %s", cell.amount, DRAGONX_TICKER);
            } else {
                if (state.market.price_usd >= 0.01)
                    snprintf(buf, sizeof(buf), "$%.4f", state.market.price_usd);
                else if (state.market.price_usd > 0)
                    snprintf(buf, sizeof(buf), "$%.8f", state.market.price_usd);
                else
                    snprintf(buf, sizeof(buf), "$--.--");
            }
            ImVec2 amtSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(cellMax.x - amtSz.x - rowPad, cy),
                        cell.isMarket ? cell.accent : (row == 0 && col == 0 ? OnSurface() : cell.accent), buf);

            // Proportion bar (shielded/transparent)
            if (cell.hasBar) {
                float barW = colW * S.drawElement(cfgSec, "bar-width-ratio").sizeOr(0.12f);
                float barH = std::max(
                    S.drawElement(cfgSec, "bar-min-height").sizeOr(3.0f),
                    rowH * S.drawElement(cfgSec, "bar-height-ratio").sizeOr(0.15f));
                float barX = cellMax.x - amtSz.x - rowPad - barW - Layout::spacingSm();
                float barY = cellMin.y + (rowH - barH) * 0.5f;
                dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
                                  IM_COL32(255, 255, 255, 15), barH * 0.5f);
                dl->AddRectFilled(ImVec2(barX, barY),
                                  ImVec2(barX + barW * cell.ratio, barY + barH),
                                  WithAlpha(cell.accent, 180), barH * 0.5f);
            }

            // Market: 24h change + sparkline
            if (cell.isMarket && state.market.price_usd > 0) {
                bool pos = state.market.change_24h >= 0;
                snprintf(buf, sizeof(buf), "%s%.1f%%", pos ? "+" : "", state.market.change_24h);
                ImVec2 chgSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
                float chgX = cellMax.x - amtSz.x - Layout::spacingLg() - chgSz.x - Layout::spacingSm();
                dl->AddText(capFont, capFont->LegacySize, ImVec2(chgX, cy),
                            pos ? Success() : Error(), buf);

                // Sparkline between label and 24h change
                if (state.market.price_history.size() >= 2) {
                    ImVec2 labelSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, cell.label);
                    float sparkGap = S.drawElement(cfgSec, "sparkline-gap").sizeOr(12.0f);
                    float sparkPad = S.drawElement(cfgSec, "sparkline-pad").sizeOr(4.0f);
                    float sparkLeft = px + labelSz.x + sparkGap;
                    float sparkRight = chgX - sparkGap;
                    if (sparkLeft < sparkRight) {
                        ImVec2 spMin(sparkLeft, cellMin.y + sparkPad);
                        ImVec2 spMax(sparkRight, cellMax.y - sparkPad);
                        ImU32 lineCol = pos
                            ? WithAlpha(Success(), 200)
                            : WithAlpha(Error(), 200);
                        DrawSparkline(dl, spMin, spMax,
                                      state.market.price_history, lineCol);
                    }
                }
            }
        }
    }

    // Advance cursor past the 2×2 grid
    float totalGridH = 2.0f * rowH + rowGap;
    ImGui::Dummy(ImVec2(availW, totalGridH));

    RenderSyncBar(app, dl, vs);
    ImGui::Dummy(ImVec2(0, cGap));

    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float addrOverride = S.drawElement(cfgSec, "address-table-height").size;
    float addrH = (addrOverride >= 0.0f) ? addrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

// ============================================================================
// Layout 5: Privacy Shield Meter
// ============================================================================
static void RenderBalanceShield(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    float glassRound = Layout::glassRounding();
    float cGap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    // Hero section
    float shieldTopMargin = S.drawElement("tabs.balance.shield", "top-margin").size;
    if (shieldTopMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, shieldTopMargin));
    else
        ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance", "compact-hero-pad").sizeOr(8.0f) * vs));
    {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TOTAL BALANCE");
        ImGui::Dummy(ImVec2(0, 2 * dp));
        snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
        ImFont* heroFont = Type().h2();
        ImVec2 pos = ImGui::GetCursorScreenPos();
        DrawTextShadow(dl, heroFont, heroFont->LegacySize, pos, OnSurface(), buf);
        ImVec2 heroSz = heroFont->CalcTextSizeA(heroFont->LegacySize, 10000.0f, 0.0f, buf);
        ImGui::Dummy(heroSz);
        ImGui::SameLine();
        ImFont* capFont = Type().caption();
        dl->AddText(capFont, capFont->LegacySize,
                    ImVec2(ImGui::GetCursorScreenPos().x, pos.y + heroSz.y - capFont->LegacySize),
                    OnSurfaceMedium(), DRAGONX_TICKER);
        ImGui::NewLine();

        double usd_value = state.getBalanceUSD();
        if (usd_value > 0.0) snprintf(buf, sizeof(buf), "$%.2f USD", usd_value);
        else snprintf(buf, sizeof(buf), "$-.-- USD");
        Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
    }
    RenderSyncBar(app, dl, vs);
    ImGui::Dummy(ImVec2(0, cGap));

    // Shield gauge panel
    {
        ImFont* shieldCap = Type().caption();
        ImFont* shieldSub1 = Type().subtitle1();
        // Font-content floor: gauge area + legend text (subtitle + 2 captions)
        float shieldFontFloor = Layout::spacingLg() * 2
            + shieldSub1->LegacySize + Layout::spacingSm()
            + shieldCap->LegacySize * 2 + Layout::spacingSm() * 2;
        float shieldCardH = S.drawElement("tabs.balance.shield", "card-height").size;
        float gaugeH;
        if (shieldCardH >= 0.0f) {
            gaugeH = std::max(shieldCardH * dp, shieldFontFloor);
        } else {
            gaugeH = std::max({shieldFontFloor,
                S.drawElement("tabs.balance.shield", "gauge-min-height").sizeOr(80.0f) * dp,
                contentAvail.y * S.drawElement("tabs.balance.shield", "gauge-height-ratio").sizeOr(0.18f)});
        }
        ImVec2 panelMin = ImGui::GetCursorScreenPos();
        ImVec2 panelMax(panelMin.x + availW, panelMin.y + gaugeH);
        GlassPanelSpec spec;
        spec.rounding = glassRound;
        DrawGlassPanel(dl, panelMin, panelMax, spec);

        float total = (float)s_dispTotal;
        float privacyRatio = (total > 1e-9f) ? (float)(s_dispShielded / total) : 0.0f;
        float privPct = privacyRatio * 100.0f;

        // Semicircle gauge
        float gaugeCx = panelMin.x + gaugeH;
        float gaugeCy = panelMin.y + gaugeH * S.drawElement("tabs.balance.shield", "gauge-center-y-ratio").sizeOr(0.7f);
        float gaugeR = std::min(
            gaugeH * S.drawElement("tabs.balance.shield", "gauge-radius-ratio").sizeOr(0.55f),
            availW * S.drawElement("tabs.balance.shield", "gauge-max-radius-ratio").sizeOr(0.15f));
        float gaugeInnerR = gaugeR * S.drawElement("tabs.balance.shield", "gauge-inner-ratio").sizeOr(0.7f);

        // Background arc (gray)
        dl->PathClear();
        dl->PathArcTo(ImVec2(gaugeCx, gaugeCy), gaugeR, IM_PI, 2.0f * IM_PI, 32);
        dl->PathArcTo(ImVec2(gaugeCx, gaugeCy), gaugeInnerR, 2.0f * IM_PI, IM_PI, 32);
        dl->PathFillConvex(IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance.shield", "gauge-bg-alpha").sizeOr(20.0f)));

        // Filled arc (colored by threshold)
        ImU32 gaugeCol;
        float goodThreshold = S.drawElement("tabs.balance.shield", "good-threshold").sizeOr(80.0f);
        float medThreshold = S.drawElement("tabs.balance.shield", "medium-threshold").sizeOr(50.0f);
        if (privPct >= goodThreshold) gaugeCol = WithAlpha(Success(), 200);
        else if (privPct >= medThreshold) gaugeCol = WithAlpha(Warning(), 200);
        else gaugeCol = WithAlpha(Error(), 200);

        float fillEnd = IM_PI + IM_PI * privacyRatio;
        if (privacyRatio > 0.01f) {
            dl->PathClear();
            dl->PathArcTo(ImVec2(gaugeCx, gaugeCy), gaugeR, IM_PI, fillEnd, 32);
            dl->PathArcTo(ImVec2(gaugeCx, gaugeCy), gaugeInnerR, fillEnd, IM_PI, 32);
            dl->PathFillConvex(gaugeCol);
        }

        // Needle line
        float needleAngle = IM_PI + IM_PI * privacyRatio;
        float needleLen = gaugeR * 0.85f;
        ImVec2 needleTip(gaugeCx + cosf(needleAngle) * needleLen,
                         gaugeCy + sinf(needleAngle) * needleLen);
        dl->AddLine(ImVec2(gaugeCx, gaugeCy), needleTip, gaugeCol,
                    S.drawElement("tabs.balance.shield", "needle-thickness").sizeOr(2.0f));

        // Center text: percentage
        ImFont* sub1 = Type().subtitle1();
        snprintf(buf, sizeof(buf), "%.0f%%", privPct);
        ImVec2 pctSz = sub1->CalcTextSizeA(sub1->LegacySize, 1000, 0, buf);
        dl->AddText(sub1, sub1->LegacySize,
                    ImVec2(gaugeCx - pctSz.x * 0.5f, gaugeCy - pctSz.y - 2 * dp),
                    gaugeCol, buf);

        // Label below gauge
        ImFont* capFont = Type().caption();
        const char* statusMsg;
        if (privPct >= 80.0f) statusMsg = "Great privacy!";
        else if (privPct >= 50.0f) statusMsg = "Consider shielding more";
        else statusMsg = "Low privacy — shield funds";
        ImVec2 msgSz = capFont->CalcTextSizeA(capFont->LegacySize, 1000, 0, statusMsg);
        dl->AddText(capFont, capFont->LegacySize,
                    ImVec2(gaugeCx - msgSz.x * 0.5f, gaugeCy + 4 * dp),
                    OnSurfaceMedium(), statusMsg);

        // Right side: balances + market
        float shieldPadOverride = S.drawElement("tabs.balance.shield", "card-padding").size;
        float shieldPad = (shieldPadOverride >= 0.0f) ? shieldPadOverride : Layout::spacingLg();
        float infoX = panelMin.x + gaugeH * 2 + shieldPad;
        float infoY = panelMin.y + shieldPad;
        ImFont* ovFont = Type().overline();

        dl->AddText(ovFont, ovFont->LegacySize, ImVec2(infoX, infoY), Success(), "SHIELDED");
        infoY += ovFont->LegacySize + 2 * dp;
        snprintf(buf, sizeof(buf), "%.8f", s_dispShielded);
        dl->AddText(capFont, capFont->LegacySize, ImVec2(infoX, infoY), Success(), buf);
        infoY += capFont->LegacySize + 6 * dp;

        dl->AddText(ovFont, ovFont->LegacySize, ImVec2(infoX, infoY), Warning(), "TRANSPARENT");
        infoY += ovFont->LegacySize + 2 * dp;
        snprintf(buf, sizeof(buf), "%.8f", s_dispTransparent);
        dl->AddText(capFont, capFont->LegacySize, ImVec2(infoX, infoY), Warning(), buf);
        infoY += capFont->LegacySize + 6 * dp;

        const auto& market = state.market;
        if (market.price_usd > 0) {
            if (market.price_usd >= 0.01)
                snprintf(buf, sizeof(buf), "$%.4f", market.price_usd);
            else
                snprintf(buf, sizeof(buf), "$%.8f", market.price_usd);
            dl->AddText(capFont, capFont->LegacySize, ImVec2(infoX, infoY),
                        OnSurfaceMedium(), buf);
        }

        ImGui::Dummy(ImVec2(availW, gaugeH));
    }

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

    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float shieldAddrOverride = S.drawElement("tabs.balance.shield", "address-table-height").size;
    float addrH = (shieldAddrOverride >= 0.0f) ? shieldAddrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

// ============================================================================
// Layout 6: Balance Timeline (placeholder — requires history tracking)
// ============================================================================
static void RenderBalanceTimeline(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    float glassRound = Layout::glassRounding();
    float cGap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    // Hero
    float tlTopMargin = S.drawElement("tabs.balance.timeline", "top-margin").size;
    if (tlTopMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, tlTopMargin));
    else
        ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance", "compact-hero-pad").sizeOr(8.0f) * vs));
    {
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TOTAL BALANCE");
        ImGui::Dummy(ImVec2(0, 2 * dp));
        snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
        ImFont* heroFont = Type().h2();
        ImVec2 pos = ImGui::GetCursorScreenPos();
        DrawTextShadow(dl, heroFont, heroFont->LegacySize, pos, OnSurface(), buf);
        ImVec2 heroSz = heroFont->CalcTextSizeA(heroFont->LegacySize, 10000.0f, 0.0f, buf);
        ImGui::Dummy(heroSz);
        ImGui::SameLine();
        ImFont* capFont = Type().caption();
        dl->AddText(capFont, capFont->LegacySize,
                    ImVec2(ImGui::GetCursorScreenPos().x, pos.y + heroSz.y - capFont->LegacySize),
                    OnSurfaceMedium(), DRAGONX_TICKER);
        ImGui::NewLine();

        double usd_value = state.getBalanceUSD();
        if (usd_value > 0.0) snprintf(buf, sizeof(buf), "$%.2f USD", usd_value);
        else snprintf(buf, sizeof(buf), "$-.-- USD");
        Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
    }
    RenderSyncBar(app, dl, vs);
    ImGui::Dummy(ImVec2(0, cGap));

    // Chart area placeholder
    {
        float tlChartH = S.drawElement("tabs.balance.timeline", "chart-height").size;
        float chartH;
        if (tlChartH >= 0.0f) {
            chartH = tlChartH * dp;  // scaled by dp for DPI + font scale
        } else {
            chartH = std::max(
                S.drawElement("tabs.balance.timeline", "chart-min-height").sizeOr(80.0f) * dp,
                contentAvail.y * S.drawElement("tabs.balance.timeline", "chart-height-ratio").sizeOr(0.20f));
        }
        ImVec2 chartMin = ImGui::GetCursorScreenPos();
        ImVec2 chartMax(chartMin.x + availW, chartMin.y + chartH);
        GlassPanelSpec spec;
        spec.rounding = glassRound;
        DrawGlassPanel(dl, chartMin, chartMax, spec);

        ImFont* capFont = Type().caption();
        const char* msg = "Balance history — collecting data...";
        ImVec2 msgSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, msg);
        dl->AddText(capFont, capFont->LegacySize,
                    ImVec2(chartMin.x + (availW - msgSz.x) * 0.5f,
                           chartMin.y + (chartH - msgSz.y) * 0.5f),
                    OnSurfaceDisabled(), msg);

        // If we have market sparkline data, use it as a preview
        const auto& market = state.market;
        if (market.price_history.size() >= 2) {
            float sparkPad = Layout::spacingLg();
            ImVec2 spMin(chartMin.x + sparkPad, chartMin.y + capFont->LegacySize + sparkPad * 2);
            ImVec2 spMax(chartMax.x - sparkPad, chartMax.y - sparkPad);
            if (spMax.y > spMin.y && spMax.x > spMin.x) {
                ImU32 lineCol = market.change_24h >= 0
                    ? WithAlpha(Success(), (int)S.drawElement("tabs.balance.timeline", "sparkline-alpha").sizeOr(120.0f))
                    : WithAlpha(Error(), (int)S.drawElement("tabs.balance.timeline", "sparkline-alpha").sizeOr(120.0f));
                DrawSparkline(dl, spMin, spMax, market.price_history, lineCol);
            }
        }

        ImGui::Dummy(ImVec2(availW, chartH));
    }

    // Compact 3 summary cards
    ImGui::Dummy(ImVec2(0, cGap));
    {
        ImFont* ovFont = Type().overline();
        ImFont* capFont = Type().caption();
        // Font-content floor: pad + overline + gap + caption + pad
        float tlPadVal = S.drawElement("tabs.balance.timeline", "card-padding").size;
        float tlPad = (tlPadVal >= 0.0f) ? tlPadVal : Layout::spacingXs();
        float tlFontFloor = tlPad + ovFont->LegacySize + 2.0f * dp + capFont->LegacySize + tlPad;
        float tlSummaryH = S.drawElement("tabs.balance.timeline", "summary-card-height").size;
        float cardH;
        if (tlSummaryH >= 0.0f) {
            cardH = std::max(tlSummaryH * dp, tlFontFloor);
        } else {
            cardH = std::max({tlFontFloor,
                S.drawElement("tabs.balance.timeline", "summary-min-height").sizeOr(44.0f) * dp,
                contentAvail.y * S.drawElement("tabs.balance.timeline", "summary-height-ratio").sizeOr(0.08f)});
        }
        float cardW = (availW - 2 * cGap) / 3.0f;
        ImVec2 origin = ImGui::GetCursorScreenPos();
        GlassPanelSpec spec;
        spec.rounding = glassRound;

        struct SumCard { const char* label; ImU32 col; double val; bool isMoney; };
        SumCard cards[3] = {
            {"SHIELDED", Success(), s_dispShielded, false},
            {"TRANSPARENT", Warning(), s_dispTransparent, false},
            {"MARKET", Primary(), state.market.price_usd, true},
        };
        for (int i = 0; i < 3; i++) {
            ImVec2 cMin(origin.x + i * (cardW + cGap), origin.y);
            ImVec2 cMax(cMin.x + cardW, cMin.y + cardH);
            DrawGlassPanel(dl, cMin, cMax, spec);

            float tlPadOverride = S.drawElement("tabs.balance.timeline", "card-padding").size;
            float cx = cMin.x + ((tlPadOverride >= 0.0f) ? tlPadOverride : Layout::spacingSm());
            float cy = cMin.y + ((tlPadOverride >= 0.0f) ? tlPadOverride : Layout::spacingXs());
            dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(), cards[i].label);
            cy += ovFont->LegacySize + 2 * dp;
            if (cards[i].isMoney) {
                if (cards[i].val >= 0.01) snprintf(buf, sizeof(buf), "$%.4f", cards[i].val);
                else if (cards[i].val > 0) snprintf(buf, sizeof(buf), "$%.8f", cards[i].val);
                else snprintf(buf, sizeof(buf), "$--.--");
            } else {
                snprintf(buf, sizeof(buf), "%.8f", cards[i].val);
            }
            dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), cards[i].col, buf);
        }
        ImGui::Dummy(ImVec2(availW, cardH));
    }

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

    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float tlAddrOverride = S.drawElement("tabs.balance.timeline", "address-table-height").size;
    float addrH = (tlAddrOverride >= 0.0f) ? tlAddrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

// ============================================================================
// Layout 7: Two-Row Hero
// ============================================================================
static void RenderBalanceTwoRow(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    float glassRound = Layout::glassRounding();
    float cGap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    // Top margin
    float twoRowTopMargin = S.drawElement("tabs.balance.two-row", "top-margin").size;
    if (twoRowTopMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, twoRowTopMargin));

    // Row 1: logo + balance + USD + actions
    RenderCompactHero(app, dl, availW, hs, vs);

    // Sync + mining on same line
    {
        const auto& state = app->state();
        ImFont* capFont = Type().caption();
        if (state.sync.syncing && state.sync.headers > 0) {
            float pct = static_cast<float>(state.sync.verification_progress) * 100.0f;
            snprintf(buf, sizeof(buf), "Syncing %.1f%%", pct);
            Type().textColored(TypeStyle::Caption, Warning(), buf);
            ImGui::SameLine();
        }
        if (state.mining.generate) {
            double hr = state.mining.localHashrate;
            if (hr >= 1000.0)
                snprintf(buf, sizeof(buf), "Mining %.1f KH/s", hr / 1000.0);
            else
                snprintf(buf, sizeof(buf), "Mining %.0f H/s", hr);
            Type().textColored(TypeStyle::Caption, WithAlpha(Success(), 200), buf);
            ImGui::SameLine();
        }

        // Action buttons right-aligned
        float btnW = S.drawElement("tabs.balance.two-row", "action-btn-width").sizeOr(80.0f);
        float rightEdge = ImGui::GetWindowWidth() - Layout::spacingLg();
        ImGui::SameLine(rightEdge - btnW * 2 - Layout::spacingSm());
        if (TactileButton("Send", ImVec2(btnW, 0), S.resolveFont("button"))) {
            app->setCurrentPage(NavPage::Send);
        }
        ImGui::SameLine();
        if (TactileButton("Receive", ImVec2(btnW, 0), S.resolveFont("button"))) {
            app->setCurrentPage(NavPage::Receive);
        }
    }

    RenderSyncBar(app, dl, vs);
    ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance.two-row", "sync-gap").sizeOr(2.0f)));

    // Row 2: 3 mini-cards inline
    {
        ImFont* twoRowCap = Type().caption();
        // Font-content floor: caption centered + vertical padding
        float twoRowFontFloor = twoRowCap->LegacySize + Layout::spacingSm() * 2;
        float twoRowCardH = S.drawElement("tabs.balance.two-row", "card-height").size;
        float miniH;
        if (twoRowCardH >= 0.0f) {
            miniH = std::max(twoRowCardH * dp, twoRowFontFloor);
        } else {
            miniH = std::max({twoRowFontFloor,
                S.drawElement("tabs.balance.two-row", "mini-min-height").sizeOr(28.0f) * dp,
                S.drawElement("tabs.balance.two-row", "mini-base-height").sizeOr(36.0f) * vs});
        }
        float miniW = (availW - 2 * cGap) / 3.0f;
        ImVec2 origin = ImGui::GetCursorScreenPos();
        GlassPanelSpec spec;
        spec.rounding = std::max(
            S.drawElement("tabs.balance.two-row", "mini-rounding-min").sizeOr(4.0f),
            glassRound * S.drawElement("tabs.balance.two-row", "mini-rounding-ratio").sizeOr(0.5f));
        ImFont* capFont = Type().caption();
        float indicatorR = S.drawElement("tabs.balance.two-row", "indicator-radius").sizeOr(3.0f);
        int balDecimals = (int)S.drawElement("tabs.balance.two-row", "balance-decimals").sizeOr(4.0f);
        float twoRowPadOverride = S.drawElement("tabs.balance.two-row", "card-padding").size;
        float miniPad = (twoRowPadOverride >= 0.0f) ? twoRowPadOverride : Layout::spacingSm();

        // Shielded mini-card
        {
            ImVec2 cMin = origin;
            ImVec2 cMax(cMin.x + miniW, cMin.y + miniH);
            DrawGlassPanel(dl, cMin, cMax, spec);
            float cx = cMin.x + miniPad;
            float cy = cMin.y + (miniH - capFont->LegacySize) * 0.5f;
            dl->AddCircleFilled(ImVec2(cx + 4 * dp, cy + capFont->LegacySize * 0.5f), indicatorR, Success());
            snprintf(buf, sizeof(buf), "%.*f", balDecimals, s_dispShielded);
            dl->AddText(capFont, capFont->LegacySize, ImVec2(cx + 12 * dp, cy), Success(), buf);

            // Percentage of total (right-aligned)
            float shieldPct = (s_dispTotal > 1e-9) ? (float)(s_dispShielded / s_dispTotal * 100.0) : 0.0f;
            snprintf(buf, sizeof(buf), "%.0f%%", shieldPct);
            ImVec2 pctSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(cMax.x - pctSz.x - miniPad, cy),
                        WithAlpha(Success(), 160), buf);
        }

        // Transparent mini-card
        {
            ImVec2 cMin(origin.x + miniW + cGap, origin.y);
            ImVec2 cMax(cMin.x + miniW, cMin.y + miniH);
            DrawGlassPanel(dl, cMin, cMax, spec);
            float cx = cMin.x + miniPad;
            float cy = cMin.y + (miniH - capFont->LegacySize) * 0.5f;
            dl->AddCircleFilled(ImVec2(cx + 4 * dp, cy + capFont->LegacySize * 0.5f), indicatorR, Warning());
            snprintf(buf, sizeof(buf), "%.*f", balDecimals, s_dispTransparent);
            dl->AddText(capFont, capFont->LegacySize, ImVec2(cx + 12 * dp, cy), Warning(), buf);

            // Percentage of total (right-aligned)
            float transPct = (s_dispTotal > 1e-9) ? (float)(s_dispTransparent / s_dispTotal * 100.0) : 0.0f;
            snprintf(buf, sizeof(buf), "%.0f%%", transPct);
            ImVec2 pctSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize,
                        ImVec2(cMax.x - pctSz.x - miniPad, cy),
                        WithAlpha(Warning(), 160), buf);
        }

        // Market mini-card
        {
            ImVec2 cMin(origin.x + 2 * (miniW + cGap), origin.y);
            ImVec2 cMax(cMin.x + miniW, cMin.y + miniH);
            DrawGlassPanel(dl, cMin, cMax, spec);
            float cx = cMin.x + miniPad;
            float cy = cMin.y + (miniH - capFont->LegacySize) * 0.5f;
            const auto& market = state.market;
            if (market.price_usd >= 0.01)
                snprintf(buf, sizeof(buf), "$%.4f", market.price_usd);
            else if (market.price_usd > 0)
                snprintf(buf, sizeof(buf), "$%.8f", market.price_usd);
            else
                snprintf(buf, sizeof(buf), "$--.--");
            ImVec2 priceSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
            dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(), buf);

            float sparkRight = cMax.x - miniPad;
            if (market.price_usd > 0) {
                bool pos = market.change_24h >= 0;
                snprintf(buf, sizeof(buf), "%s%.1f%%", pos ? "+" : "", market.change_24h);
                ImVec2 chgSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
                float chgX = cMax.x - chgSz.x - miniPad;
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(chgX, cy),
                            pos ? Success() : Error(), buf);
                sparkRight = chgX;

                // Sparkline between price and percentage
                if (market.price_history.size() >= 2) {
                    float sparkGap = S.drawElement("tabs.balance.two-row", "sparkline-gap").sizeOr(6.0f);
                    float sparkPad = S.drawElement("tabs.balance.two-row", "sparkline-pad").sizeOr(4.0f);
                    float sparkLeft = cx + priceSz.x + sparkGap;
                    float sparkRightEdge = sparkRight - sparkGap;
                    if (sparkLeft < sparkRightEdge) {
                        ImVec2 spMin(sparkLeft, cMin.y + sparkPad);
                        ImVec2 spMax(sparkRightEdge, cMax.y - sparkPad);
                        ImU32 lineCol = pos
                            ? WithAlpha(Success(), 200)
                            : WithAlpha(Error(), 200);
                        DrawSparkline(dl, spMin, spMax,
                                      market.price_history, lineCol);
                    }
                }
            }
        }

        ImGui::Dummy(ImVec2(availW, miniH));
    }

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

    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float twoRowAddrOverride = S.drawElement("tabs.balance.two-row", "address-table-height").size;
    float addrH = (twoRowAddrOverride >= 0.0f) ? twoRowAddrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

// ============================================================================
// Layout 8: Minimal (card-less typography only)
// ============================================================================
static void RenderBalanceMinimal(App* app) {
    using namespace material;
    const auto& S = schema::UISchema::instance();
    const auto& state = app->state();
    UpdateBalanceLerp(app);

    ImVec2 contentAvail = ImGui::GetContentRegionAvail();
    float availW = contentAvail.x;
    float hs = Layout::hScale(availW);
    float vs = Layout::vScale(contentAvail.y);
    float glassRound = Layout::glassRounding();
    const float dp = Layout::dpiScale();
    ImDrawList* dl = ImGui::GetWindowDrawList();
    char buf[64];

    // Top margin
    float minTopMargin = S.drawElement("tabs.balance.minimal", "top-margin").size;
    if (minTopMargin >= 0.0f)
        ImGui::Dummy(ImVec2(0, minTopMargin));

    ImFont* heroFont = Type().h2();
    ImFont* sub1 = Type().subtitle1();
    ImFont* capFont = Type().caption();

    // Line 1: Big balance + market price
    snprintf(buf, sizeof(buf), "%.8f %s", s_dispTotal, DRAGONX_TICKER);
    ImVec2 pos = ImGui::GetCursorScreenPos();
    DrawTextShadow(dl, heroFont, heroFont->LegacySize, pos, OnSurface(), buf);
    ImVec2 heroSz = heroFont->CalcTextSizeA(heroFont->LegacySize, 10000.0f, 0.0f, buf);

    // Market price right-aligned
    const auto& market = state.market;
    if (market.price_usd > 0) {
        if (market.price_usd >= 0.01)
            snprintf(buf, sizeof(buf), "$%.4f", market.price_usd);
        else
            snprintf(buf, sizeof(buf), "$%.8f", market.price_usd);
        ImVec2 pSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
        float rightEdge = pos.x + availW;
        dl->AddText(capFont, capFont->LegacySize,
                    ImVec2(rightEdge - pSz.x, pos.y + heroSz.y - capFont->LegacySize),
                    OnSurfaceMedium(), buf);
    }
    ImGui::Dummy(heroSz);

    // Line 2: Shielded + Transparent
    snprintf(buf, sizeof(buf), "Shielded: %.8f", s_dispShielded);
    Type().textColored(TypeStyle::Caption, Success(), buf);
    ImGui::SameLine(0, Layout::spacingLg());
    snprintf(buf, sizeof(buf), "Transparent: %.8f", s_dispTransparent);
    Type().textColored(TypeStyle::Caption, Warning(), buf);

    // USD value
    double usd_value = state.getBalanceUSD();
    if (usd_value > 0.0) snprintf(buf, sizeof(buf), "$%.2f USD", usd_value);
    else snprintf(buf, sizeof(buf), "$-.-- USD");
    ImGui::SameLine(0, Layout::spacingLg());
    Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);

    RenderSyncBar(app, dl, vs);

    // Dashed separator
    {
        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
        ImVec2 sepPos = ImGui::GetCursorScreenPos();
        float dashLen = S.drawElement("tabs.balance.minimal", "dash-length").sizeOr(6.0f);
        float gapLen = S.drawElement("tabs.balance.minimal", "dash-gap").sizeOr(4.0f);
        float sepAlpha = S.drawElement("tabs.balance.minimal", "separator-alpha").sizeOr(25.0f);
        float sepThick = S.drawElement("tabs.balance.minimal", "separator-thickness").sizeOr(1.0f);
        float x = sepPos.x;
        float endX = sepPos.x + availW;
        while (x < endX) {
            float x2 = std::min(x + dashLen, endX);
            dl->AddLine(ImVec2(x, sepPos.y), ImVec2(x2, sepPos.y),
                        IM_COL32(255, 255, 255, (int)sepAlpha), sepThick);
            x += dashLen + gapLen;
        }
        ImGui::Dummy(ImVec2(availW, 1));
        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
    }

    float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f);
    float minAddrOverride = S.drawElement("tabs.balance.minimal", "address-table-height").size;
    float addrH = (minAddrOverride >= 0.0f) ? minAddrOverride * dp
        : ImGui::GetContentRegionAvail().y - recentReserve
                  - Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
    RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
    RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}

} // namespace ui
} // namespace dragonx
