feat(ui): in-app FAQ, DPI-scaling audit fixes, mining/stratum polish + localize strings

Bundles the session's UI work (the touched files carry several of these
changes together, so they are committed as one coherent UI batch):

- FAQ: new RenderFaqDialog + data-driven faq_content, opened from a
  status-bar "?" (and the Windows title bar), styled like the Wallets
  modal with search, Wallet/Daemon tabs, and smooth scroll.
- DPI/font-scale audit: multiply hand-drawn absolute geometry by
  Layout::dpiScale() across ~30 files so nothing renders native-size at
  HiDPI / font_scale 1.5 (verified with a full sweep at 1.5x).
- Mining: chart now fills the horizontal space; thread stepper +/- buttons
  match the input-box height; move the stratum-host toggle into
  Node & Security (v1.3.0+).
- Settings: fix the auto-shield status text overlapping the grid.
- Sidebar: drop the peer-count badge on the Network button.
- i18n: wrap 193 hardcoded literals with TR() (keys/translations added in
  the preceding i18n commit), so the security/PIN/lock flow, seed-backup
  wizard, and witness-rebuild dialog localize.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 19:37:16 -05:00
co-authored by Claude Opus 4.8
parent ed675f90d8
commit d27f387d6d
35 changed files with 867 additions and 366 deletions
+51 -51
View File
@@ -302,9 +302,9 @@ static void RenderBalanceClassic(App* app)
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);
const float ovGap = S.drawElement("tabs.balance", "overline-value-gap").sizeOr(6.0f) * dp;
const float valGap = S.drawElement("tabs.balance", "value-caption-gap").sizeOr(4.0f) * dp;
const float tickGap = S.drawElement("tabs.balance.classic", "ticker-gap").sizeOr(4.0f) * dp;
float marketContentH = cardPadLg
+ ovFont->LegacySize + ovGap
+ sub1->LegacySize + 2.0f * dp
@@ -323,7 +323,7 @@ static void RenderBalanceClassic(App* app)
// 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);
const float accentW = S.drawElement("tabs.balance", "accent-width").sizeOr(4.0f) * dp;
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,
@@ -446,7 +446,7 @@ static void RenderBalanceClassic(App* app)
ImU32 bd = WithAlpha(fg, 90);
snprintf(buf, sizeof(buf), "%s %s",
TR("data_stale_prefix"), timeAgo(state.last_balance_update).c_str());
ImVec2 pillSz = DrawPill(dl, ImVec2(cx, cy), buf, capFont, fg, bg, bd);
ImVec2 pillSz = DrawPill(dl, ImVec2(cx, cy), buf, capFont, fg, bg, bd, ImVec2(4.0f * dp, 2.0f * dp));
// Hover → explain what stale means and how old the data actually is.
if (material::IsRectHovered(ImVec2(cx, cy), ImVec2(cx + pillSz.x, cy + pillSz.y)))
Tooltip("%s", TR("data_stale_tooltip"));
@@ -456,7 +456,7 @@ static void RenderBalanceClassic(App* app)
// 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));
cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f) * dp);
}
}
@@ -487,7 +487,7 @@ static void RenderBalanceClassic(App* app)
{
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",
snprintf(buf, sizeof(buf), TR("baltab_pct_of_total_zaddr"),
privPct, (int)state.z_addresses.size());
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy),
WithAlpha(Success(), 160), buf);
@@ -498,8 +498,8 @@ static void RenderBalanceClassic(App* app)
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);
float bp = S.drawElement("tabs.balance.classic", "unconfirmed-badge-padding").sizeOr(4.0f) * dp;
float br = S.drawElement("tabs.balance.classic", "unconfirmed-badge-rounding").sizeOr(4.0f) * dp;
ImVec2 bMin(cMax.x - ts.x - bp * 3,
cMin.y + cardPadLg);
ImVec2 bMax(cMax.x - bp, bMin.y + ts.y + bp);
@@ -513,7 +513,7 @@ static void RenderBalanceClassic(App* app)
// 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));
cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f) * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (ImGui::IsMouseClicked(0))
app->setCurrentPage(NavPage::Receive);
@@ -543,7 +543,7 @@ static void RenderBalanceClassic(App* app)
OnSurfaceMedium(), DRAGONX_TICKER);
cy += sub1->LegacySize + valGap;
snprintf(buf, sizeof(buf), "%d T-addresses",
snprintf(buf, sizeof(buf), TR("baltab_t_addresses_count"),
(int)state.t_addresses.size());
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy),
OnSurfaceDisabled(), buf);
@@ -551,7 +551,7 @@ static void RenderBalanceClassic(App* app)
// 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));
cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f) * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (ImGui::IsMouseClicked(0))
app->setCurrentPage(NavPage::Receive);
@@ -589,7 +589,7 @@ static void RenderBalanceClassic(App* app)
[](unsigned char c){ return (char)std::toupper(c); });
float textW = std::max(pSz.x + tickGap + usdSz.x,
ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, marketLabel.c_str()).x);
float sparkGap = S.drawElement("tabs.balance.classic", "sparkline-gap").sizeOr(12.0f);
float sparkGap = S.drawElement("tabs.balance.classic", "sparkline-gap").sizeOr(12.0f) * dp;
float sparkLeft = cx + textW + sparkGap;
float sparkRight = cMax.x - cardPadLg;
@@ -611,7 +611,7 @@ static void RenderBalanceClassic(App* app)
bool pos = market.change_24h >= 0;
ImU32 chgCol = pos ? Success()
: Error();
snprintf(buf, sizeof(buf), "%s%.1f%% 24h",
snprintf(buf, sizeof(buf), TR("baltab_pct_change_24h"),
pos ? "+" : "", market.change_24h);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(cx, cy), chgCol, buf);
@@ -633,7 +633,7 @@ static void RenderBalanceClassic(App* app)
// 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));
cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f) * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (ImGui::IsMouseClicked(0))
app->setCurrentPage(NavPage::Market);
@@ -790,20 +790,20 @@ static void RenderBalanceDonut(App* app) {
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);
float legendDotR = S.drawElement("tabs.balance.donut", "legend-dot-radius").sizeOr(4.0f) * dp;
float legendXOff = S.drawElement("tabs.balance.donut", "legend-x-offset").sizeOr(14.0f) * dp;
float legendLineGap = S.drawElement("tabs.balance.donut", "legend-line-gap").sizeOr(6.0f) * dp;
float legendSectionGap = S.drawElement("tabs.balance.donut", "legend-section-gap").sizeOr(10.0f) * dp;
// Shielded legend
dl->AddCircleFilled(ImVec2(legendX + 5 * dp, legendY + capFont->LegacySize * 0.5f), legendDotR, Success());
snprintf(buf, sizeof(buf), "Shielded %.8f", s_dispShielded);
snprintf(buf, sizeof(buf), TR("baltab_shielded_amount"), 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);
snprintf(buf, sizeof(buf), TR("baltab_transparent_amount"), s_dispTransparent);
dl->AddText(capFont, capFont->LegacySize, ImVec2(legendX + legendXOff, legendY), Warning(), buf);
legendY += capFont->LegacySize + legendSectionGap;
@@ -811,15 +811,15 @@ static void RenderBalanceDonut(App* app) {
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);
snprintf(buf, sizeof(buf), TR("baltab_market_price_4dp"), market.price_usd);
else
snprintf(buf, sizeof(buf), "Market: $%.8f", market.price_usd);
snprintf(buf, sizeof(buf), TR("baltab_market_price_8dp"), 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);
snprintf(buf, sizeof(buf), TR("baltab_pct_change_24h"), pos ? "+" : "", market.change_24h);
dl->AddText(capFont, capFont->LegacySize, ImVec2(legendX + legendXOff, legendY),
pos ? Success() : Error(), buf);
}
@@ -946,7 +946,7 @@ static void RenderBalanceConsolidated(App* app) {
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));
S.drawElement("tabs.balance.consolidated", "divider-thickness").sizeOr(1.0f) * dp);
// Bottom half: proportion bars
float barY = divY + Layout::spacingSm();
@@ -1122,7 +1122,7 @@ static void RenderBalanceDashboard(App* app) {
// Accent stripe — clipped to tile rounded corners
{
float aw = S.drawElement("tabs.balance", "accent-width").sizeOr(4.0f);
float aw = S.drawElement("tabs.balance", "accent-width").sizeOr(4.0f) * dp;
dl->PushClipRect(tMin, ImVec2(tMin.x + aw, tMax.y), true);
dl->AddRectFilled(tMin, tMax, tiles[i].accent, tileSpec.rounding,
ImDrawFlags_RoundCornersLeft);
@@ -1159,7 +1159,7 @@ static void RenderBalanceDashboard(App* app) {
// 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));
tileSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f) * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (ImGui::IsMouseClicked(0))
app->setCurrentPage(tiles[i].nav);
@@ -1208,7 +1208,7 @@ static void RenderBalanceVerticalStack(App* app) {
// 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 rowGap = S.drawElement("tabs.balance.vertical-stack", "row-gap").sizeOr(2.0f) * dp;
float vstackFontFloor = vstackRowFontFloor * 4 + rowGap * 3;
float vstackCardH = S.drawElement("tabs.balance.vertical-stack", "card-height").size;
float stackH;
@@ -1238,10 +1238,10 @@ static void RenderBalanceVerticalStack(App* app) {
};
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},
{TR("baltab_total_balance"), ICON_MD_ACCOUNT_BALANCE_WALLET, S.resolveColor("var(--accent-total)", OnSurface()), s_dispTotal, 1.0f},
{TR("baltab_shielded"), ICON_MD_SHIELD, S.resolveColor("var(--accent-shielded)", Success()), s_dispShielded, shieldRatio},
{TR("baltab_transparent"), ICON_MD_CIRCLE, S.resolveColor("var(--accent-transparent)", Warning()), s_dispTransparent, transRatio},
{TR("baltab_market"), ICON_MD_TRENDING_UP, S.resolveColor("var(--accent-action)", Primary()), state.market.price_usd, 0.0f},
};
for (int i = 0; i < 4; i++) {
@@ -1296,7 +1296,7 @@ static void RenderBalanceVerticalStack(App* app) {
// 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 barGap = S.drawElement("tabs.balance.vertical-stack", "sparkline-gap").sizeOr(12.0f) * dp;
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),
@@ -1326,8 +1326,8 @@ static void RenderBalanceVerticalStack(App* app) {
// 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 sparkGap = S.drawElement("tabs.balance.vertical-stack", "sparkline-gap").sizeOr(12.0f) * dp;
float sparkPad = S.drawElement("tabs.balance.vertical-stack", "sparkline-pad").sizeOr(4.0f) * dp;
float sparkLeft = px + labelSz.x + sparkGap;
float sparkRight = chgX - sparkGap;
if (sparkLeft < sparkRight) {
@@ -1387,8 +1387,8 @@ static void RenderBalanceVertical2x2(App* app) {
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 rowGap = S.drawElement(cfgSec, "row-gap").sizeOr(2.0f) * dp;
float colGap = S.drawElement(cfgSec, "col-gap").sizeOr(8.0f) * dp;
float v2x2FontFloor = v2x2RowFontFloor * 2 + rowGap;
float cardHOverride = S.drawElement(cfgSec, "card-height").size;
float stackH;
@@ -1431,13 +1431,13 @@ static void RenderBalanceVertical2x2(App* app) {
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},
{TR("baltab_total_balance"), ICON_MD_ACCOUNT_BALANCE_WALLET, S.resolveColor("var(--accent-total)", OnSurface()), s_dispTotal, 1.0f, false, false},
{TR("baltab_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},
{TR("baltab_market"), ICON_MD_TRENDING_UP, S.resolveColor("var(--accent-action)", Primary()), state.market.price_usd, 0.0f, true, false},
{TR("baltab_transparent"), ICON_MD_CIRCLE, S.resolveColor("var(--accent-transparent)", Warning()), s_dispTransparent, transRatio, false, true},
},
};
@@ -1519,8 +1519,8 @@ static void RenderBalanceVertical2x2(App* app) {
// 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 sparkGap = S.drawElement(cfgSec, "sparkline-gap").sizeOr(12.0f) * dp;
float sparkPad = S.drawElement(cfgSec, "sparkline-pad").sizeOr(4.0f) * dp;
float sparkLeft = px + labelSz.x + sparkGap;
float sparkRight = chgX - sparkGap;
if (sparkLeft < sparkRight) {
@@ -1665,7 +1665,7 @@ static void RenderBalanceShield(App* app) {
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));
S.drawElement("tabs.balance.shield", "needle-thickness").sizeOr(2.0f) * dp);
// Center text: percentage
ImFont* sub1 = Type().subtitle1();
@@ -1956,7 +1956,7 @@ static void RenderBalanceTwoRow(App* app) {
}
RenderSyncBar(app, dl, vs);
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance.two-row", "sync-gap").sizeOr(2.0f)));
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance.two-row", "sync-gap").sizeOr(2.0f) * dp));
// Row 2: 3 mini-cards inline
{
@@ -1979,7 +1979,7 @@ static void RenderBalanceTwoRow(App* app) {
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);
float indicatorR = S.drawElement("tabs.balance.two-row", "indicator-radius").sizeOr(3.0f) * dp;
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();
@@ -2054,8 +2054,8 @@ static void RenderBalanceTwoRow(App* app) {
// 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 sparkGap = S.drawElement("tabs.balance.two-row", "sparkline-gap").sizeOr(6.0f) * dp;
float sparkPad = S.drawElement("tabs.balance.two-row", "sparkline-pad").sizeOr(4.0f) * dp;
float sparkLeft = cx + priceSz.x + sparkGap;
float sparkRightEdge = sparkRight - sparkGap;
if (sparkLeft < sparkRightEdge) {
@@ -2153,10 +2153,10 @@ static void RenderBalanceMinimal(App* app) {
{
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 dashLen = S.drawElement("tabs.balance.minimal", "dash-length").sizeOr(6.0f) * dp;
float gapLen = S.drawElement("tabs.balance.minimal", "dash-gap").sizeOr(4.0f) * dp;
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 sepThick = S.drawElement("tabs.balance.minimal", "separator-thickness").sizeOr(1.0f) * dp;
float x = sepPos.x;
float endX = sepPos.x + availW;
while (x < endX) {