diff --git a/frontend/index.html b/frontend/index.html index f904f23..6c08dba 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -797,6 +797,16 @@

APY Alerts

+ + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts index fcc1ceb..b54a534 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1703,6 +1703,7 @@ function showConnected() { $("dashboard").classList.remove("hidden"); $("asset-tabs-bar").style.display = ""; } + syncMobileActionBar(); } async function connect() { @@ -1818,6 +1819,7 @@ function switchView(view: AppView) { closeDrawer(); // Close pool dropdown $("pool-dropdown").classList.add("hidden"); + syncMobileActionBar(); } // ── Mobile sidebar drawer (#5) ─────────────────────────────────────────── @@ -2255,11 +2257,77 @@ $("demo-btn").addEventListener("click", () => { toast("Demo mode \u2014 explore the UI without a wallet", "info"); }); +// ── Mobile sticky action bar (A22) ─────────────────────────────────────────── + +/** Mirror the state of in-card primary CTAs to the sticky bottom bar. */ +function syncMobileActionBar() { + const bar = $("mobile-action-bar"); + // Only show on the leverage view when dashboard is visible + const visible = activeView === "leverage" && !$("dashboard").classList.contains("hidden"); + bar.classList.toggle("hidden", !visible); + if (!visible) return; + + const openBtn = $("open-btn") as HTMLButtonElement; + const adjustBtn = $("adjust-btn") as HTMLButtonElement; + const addFundsBtn = $("add-funds-btn") as HTMLButtonElement; + const closeBtn = $("close-btn") as HTMLButtonElement; + const repayBtn = $("repay-btn") as HTMLButtonElement; + + const mobOpen = $("mob-open-btn") as HTMLButtonElement; + const mobAdjust = $("mob-adjust-btn") as HTMLButtonElement; + const mobAddFunds = $("mob-add-funds-btn") as HTMLButtonElement; + const mobClose = $("mob-close-btn") as HTMLButtonElement; + const mobRepay = $("mob-repay-btn") as HTMLButtonElement; + + // Sync visibility + mobOpen.classList.toggle("hidden", openBtn.classList.contains("hidden")); + mobAdjust.classList.toggle("hidden", adjustBtn.classList.contains("hidden")); + mobAddFunds.classList.toggle("hidden", addFundsBtn.classList.contains("hidden")); + // Close/Repay always shown when position exists + const hasPos = !$("no-position").classList.contains("hidden") === false; + mobClose.classList.toggle("hidden", closeBtn.disabled && !hasPos); + mobRepay.classList.toggle("hidden", true); // only show repay when enabled + + // Sync disabled state + mobOpen.disabled = openBtn.disabled; + mobAdjust.disabled = adjustBtn.disabled; + mobAddFunds.disabled = addFundsBtn.disabled; + mobClose.disabled = closeBtn.disabled; + mobRepay.disabled = repayBtn.disabled; + + // Sync text + mobAdjust.textContent = adjustBtn.textContent; + mobAddFunds.textContent = addFundsBtn.textContent; + + // Show close/repay only when position exists + const posExists = $("position-data") && !$("position-data").classList.contains("hidden"); + mobClose.classList.toggle("hidden", !posExists); + mobRepay.classList.toggle("hidden", repayBtn.disabled); +} + +$("mob-open-btn").addEventListener("click", () => ($("open-btn") as HTMLButtonElement).click()); +$("mob-adjust-btn").addEventListener("click", () => ($("adjust-btn") as HTMLButtonElement).click()); +$("mob-add-funds-btn").addEventListener("click",() => ($("add-funds-btn") as HTMLButtonElement).click()); +$("mob-close-btn").addEventListener("click", () => ($("close-btn") as HTMLButtonElement).click()); +$("mob-repay-btn").addEventListener("click", () => ($("repay-btn") as HTMLButtonElement).click()); + +// Patch updatePreview and renderPosition to also sync the bar +const _origUpdatePreview = updatePreview; +// We can't reassign const, so we call syncMobileActionBar at the end of the +// existing event-driven flow by observing MutationObserver on the buttons. +const _barObserver = new MutationObserver(syncMobileActionBar); +["open-btn","adjust-btn","add-funds-btn","close-btn","repay-btn"].forEach(id => { + _barObserver.observe($(id), { attributes: true, attributeFilter: ["disabled","class"] }); +}); +// Also sync on view switch +const _origSwitchView = switchView; + // Init preview with defaults updatePreview(); renderTxHistory(); renderPoolFooter(); initTooltips(); +syncMobileActionBar(); // ── Overview (cross-protocol dashboard) ─────────────────────────────────────── diff --git a/frontend/src/style.css b/frontend/src/style.css index 0d4348f..6245121 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -1210,3 +1210,111 @@ main { flex: 1; max-width: 1200px; width: 100%; margin: 0 auto; padding: 20px 24 *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } .skeleton { animation: none; background: var(--metric-bg); } } + +/* ── Mobile-first 375px rework (A22) ─────────────────────────────────────── */ + +/* Sticky bottom action bar */ +.mobile-action-bar { + display: none; + position: fixed; bottom: 0; left: 0; right: 0; z-index: 120; + padding: 10px 12px calc(10px + env(safe-area-inset-bottom)); + background: var(--surface); border-top: 1px solid var(--border); + gap: 8px; +} +.mob-cta-btn { + flex: 1; min-height: 48px; font-size: 15px; font-weight: 700; + border-radius: var(--r); +} + +@media (max-width: 480px) { + /* Show sticky bar, hide in-card primary CTAs */ + .mobile-action-bar { display: flex; } + #open-btn, #adjust-btn, #add-funds-btn { display: none !important; } + + /* Pad main so content isn't hidden behind sticky bar */ + main { padding-bottom: 90px; } + + /* Fix .two-col overflow — was minmax(340px,380px) which exceeds 375px */ + .two-col { grid-template-columns: 1fr; } + + /* Stats row: 2-col at 480px, already handled; ensure no overflow */ + .stats-row { grid-template-columns: 1fr 1fr; } + + /* Slider zones: abbreviate labels to prevent overflow */ + .slider-zones { font-size: 9px; letter-spacing: 0; padding: 0; } + .zone-conservative::after { content: 'Safe'; } + .zone-conservative > * { display: none; } + .zone-conservative { font-size: 0; } + .zone-conservative::after { font-size: 9px; font-weight: 600; color: var(--success); } + .zone-moderate::after { content: 'Mod'; } + .zone-moderate { font-size: 0; } + .zone-moderate::after { font-size: 9px; font-weight: 600; color: var(--primary); } + .zone-aggressive::after { content: 'Agg'; } + .zone-aggressive { font-size: 0; } + .zone-aggressive::after { font-size: 9px; font-weight: 600; color: var(--warning); } + .zone-degen::after { content: 'Degen'; } + .zone-degen { font-size: 0; } + .zone-degen::after { font-size: 9px; font-weight: 600; color: var(--danger); } + .zone-maxi-degen::after { content: 'MAX'; } + .zone-maxi-degen { font-size: 0; } + .zone-maxi-degen::after { font-size: 9px; font-weight: 600; color: var(--danger); } + + /* Slider row: stack number input below slider */ + .slider-row { flex-wrap: wrap; gap: 8px; } + .slider { min-width: 0; flex: 1 1 100%; order: 1; } + .leverage-num-input { order: 2; width: 72px; flex: 0 0 auto; } + .slider-value-x { order: 3; } + + /* Slider thumb: enlarge to 44px touch target */ + .slider::-webkit-slider-thumb { + width: 28px; height: 28px; + box-shadow: 0 0 0 8px var(--slider-glow); + } + .slider::-moz-range-thumb { + width: 28px; height: 28px; border-radius: 50%; + background: var(--primary); border: none; cursor: pointer; + } + + /* Asset tabs: min 44px height */ + .asset-tab { + min-height: 44px; padding: 0 14px; + display: flex; align-items: center; + } + + /* Adjust sub-tabs: min 44px height */ + .adjust-tab { min-height: 44px; padding: 0; } + + /* Mobile card tabs: already 10px padding, ensure 44px */ + .mobile-card-tab { min-height: 44px; } + + /* Pool tabs in sidebar: min 44px */ + .pool-tab { min-height: 44px; } + + /* btn-sm: increase tap target */ + .btn-sm { min-height: 36px; padding: 6px 12px; } + + /* BLND actions: wrap on small screens */ + .blnd-actions { flex-wrap: wrap; gap: 4px; } + .blnd-actions .btn { flex: 1; min-height: 44px; font-size: 12px; } + + /* Position actions: full-width buttons */ + .position-actions { flex-direction: column; } + .position-actions .btn { width: 100%; min-height: 44px; } + + /* Wallet connected: hide switch/disconnect on very small screens, keep address */ + .wallet-connected #switch-wallet-btn, + .wallet-connected #disconnect-btn { display: none; } + + /* Prevent any element from causing horizontal overflow */ + body { overflow-x: hidden; } + #app, .main-wrap, main { max-width: 100%; overflow-x: hidden; } + + /* Overview table: scroll horizontally within its container */ + .overview-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; } + .overview-table { min-width: 480px; } +} + +/* Wrap overview table for horizontal scroll on mobile */ +@media (max-width: 600px) { + .overview-table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; } +}