Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ <h2 id="disclaimer-title">Important Disclaimer</h2>
<div id="settings-dropdown" class="settings-dropdown hidden">
<button id="expert-toggle" class="settings-dropdown-item">Expert Mode <span class="settings-badge">Off</span></button>
<button id="theme-toggle" class="settings-dropdown-item">Theme <span class="settings-badge">&#9790;</span></button>
<label class="settings-field" for="safe-max-floor">
<span>Safe Max HF</span>
<input type="number" id="safe-max-floor" class="settings-number mono" min="1.01" max="10" step="0.01" value="1.20" />
</label>
</div>
<button id="network-toggle" class="btn btn-ghost btn-sm network-toggle" data-tip="Switch between Mainnet and Testnet">Mainnet</button>
<button id="connect-btn" class="btn btn-primary btn-connect">Connect Wallet</button>
Expand Down Expand Up @@ -396,6 +400,7 @@ <h2 id="action-card-title">Open Position</h2>
<input type="range" id="leverage-slider" min="1.1" max="12.9" step="0.1" value="2.0" class="slider" />
<input type="number" id="leverage-input" class="input mono leverage-num-input" min="1.1" max="12.9" step="0.1" value="2.0" />
<span class="slider-value-x mono">&times;</span>
<button type="button" id="safe-max-btn" class="btn btn-ghost btn-sm safe-max-btn">Safe Max</button>
</div>
<div class="slider-zones" id="slider-zones">
<span class="slider-zone zone-conservative" data-zone="conservative">Conservative</span>
Expand Down
66 changes: 66 additions & 0 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,26 @@ const fmt = (n: number, d = 2) =>
const aprToApy = (apr: number) => (Math.exp(apr / 100) - 1) * 100;
const fmtAddr = (addr: string) => addr.slice(0, 6) + "…" + addr.slice(-4);

// ── Safe Max settings ─────────────────────────────────────────────────────────

const SAFE_MAX_HF_FLOOR_KEY = "safeMaxHfFloor";
const DEFAULT_SAFE_MAX_HF_FLOOR = 1.2;

function sanitizeSafeMaxFloor(value: number): number {
if (!Number.isFinite(value)) return DEFAULT_SAFE_MAX_HF_FLOOR;
return Math.min(10, Math.max(MIN_HF_NORMAL, Math.round(value * 1000) / 1000));
}

let safeMaxHfFloor = sanitizeSafeMaxFloor(
parseFloat(localStorage.getItem(SAFE_MAX_HF_FLOOR_KEY) ?? String(DEFAULT_SAFE_MAX_HF_FLOOR))
);

function renderSafeMaxFloorInput() {
const input = $("safe-max-floor") as HTMLInputElement;
input.value = safeMaxHfFloor.toFixed(2);
input.min = MIN_HF_NORMAL.toFixed(2);
}

// ── Skeleton loading (#3) ────────────────────────────────────────────────────

function setSkeleton(id: string) {
Expand Down Expand Up @@ -610,6 +630,8 @@ function updateLeverageSlider(c: number, l: number = 1) {
slider.max = numIn.max = String(leverageable ? maxLev : 1.0);
slider.step = numIn.step = "0.1";
slider.disabled = numIn.disabled = !leverageable;
const safeMaxBtn = document.getElementById("safe-max-btn") as HTMLButtonElement | null;
if (safeMaxBtn) safeMaxBtn.disabled = !leverageable;
const cur = parseFloat(slider.value);
const clamped = Math.min(parseFloat(slider.max), Math.max(1.0, cur));
if (clamped !== cur) { slider.value = String(clamped); numIn.value = String(clamped); }
Expand Down Expand Up @@ -1167,6 +1189,40 @@ function switchAdjustSubTab(sub: "leverage" | "add-funds") {

// ── Leverage preview ──────────────────────────────────────────────────────────

function saveSafeMaxFloorFromInput() {
const input = $("safe-max-floor") as HTMLInputElement;
const parsed = parseFloat(input.value);
const next = sanitizeSafeMaxFloor(parsed);
safeMaxHfFloor = next;
localStorage.setItem(SAFE_MAX_HF_FLOOR_KEY, String(next));
renderSafeMaxFloorInput();
updatePreview();
}

function applySafeMaxLeverage() {
const slider = $("leverage-slider") as HTMLInputElement;
const numIn = $("leverage-input") as HTMLInputElement;
const rs = reserves.find(r => r.asset.id === selectedAsset.id);
const c = rs ? rs.cFactor : selectedAsset.cFactor;
const l = rs?.lFactor ?? 1;
const floor = Math.max(safeMaxHfFloor, minHF());
const sliderMax = parseFloat(slider.max) || 1.0;
const rawMax = Math.min(sliderMax, maxLeverageFor(c, l, floor));
const steppedMax = Math.floor(rawMax * 10) / 10;
const next = Math.max(1.0, steppedMax);

if (!Number.isFinite(next) || next <= 1.0) {
slider.value = numIn.value = "1.0";
updatePreview();
toast(`HF floor ${fmt(floor, 2)} only allows 1.0x leverage.`, "info");
return;
}

slider.value = numIn.value = next.toFixed(1);
updatePreview();
toast(`Safe Max set to ${next.toFixed(1)}x for HF floor ${fmt(floor, 2)}.`, "success");
}

function updatePreview() {
const slider = $("leverage-slider") as HTMLInputElement;
const numIn = $("leverage-input") as HTMLInputElement;
Expand Down Expand Up @@ -2023,12 +2079,21 @@ function toggleTheme() {
$("theme-toggle").addEventListener("click", toggleTheme);
document.getElementById("mobile-theme-toggle")?.addEventListener("click", toggleTheme);

renderSafeMaxFloorInput();
$("safe-max-floor").addEventListener("change", saveSafeMaxFloorFromInput);
$("safe-max-floor").addEventListener("keydown", (e) => {
if ((e as KeyboardEvent).key === "Enter") {
(e.target as HTMLInputElement).blur();
}
});

// Settings dropdown toggle
$("settings-btn").addEventListener("click", (e) => {
e.stopPropagation();
$("settings-dropdown").classList.toggle("hidden");
$("pool-dropdown").classList.add("hidden");
});
$("settings-dropdown").addEventListener("click", (e) => e.stopPropagation());

// Network toggle
$("network-toggle").addEventListener("click", () => {
Expand Down Expand Up @@ -2201,6 +2266,7 @@ async function refreshAddFundsBalance() {
}

($("leverage-slider") as HTMLInputElement).addEventListener("input", updatePreview);
$("safe-max-btn").addEventListener("click", applySafeMaxLeverage);
// Live preview while typing (no clamping so user can type multi-digit numbers like "10")
($("leverage-input") as HTMLInputElement).addEventListener("input", () => {
const numIn = $("leverage-input") as HTMLInputElement;
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,16 @@ body {
}
.settings-dropdown-item:hover { background: var(--tab-hover-bg); color: var(--text); }
.settings-badge { font-size: 10px; padding: 1px 6px; border-radius: 99px; background: var(--surface2); color: var(--text-3); }
.settings-field {
display: flex; align-items: center; justify-content: space-between; gap: 10px;
padding: 8px 12px; color: var(--text-2); font-size: 13px; font-weight: 500;
}
.settings-number {
width: 66px; padding: 4px 6px;
background: var(--input-bg); border: 1px solid var(--border); border-radius: var(--r-xs);
color: var(--text); font-size: 12px; text-align: right; outline: none;
}
.settings-number:focus { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-glow); }

/* Legacy sidebar — hidden by default, used for mobile drawer */
.sidebar { display: none; }
Expand Down Expand Up @@ -606,6 +616,7 @@ main { flex: 1; max-width: 1200px; width: 100%; margin: 0 auto; padding: 20px 24
.slider::-webkit-slider-thumb:hover { background: var(--primary-h); box-shadow: 0 0 0 6px var(--slider-glow-h); }
.leverage-num-input { width: 68px; padding: 5px 6px; font-size: 15px; text-align: right; padding-right: 4px; }
.slider-value-x { font-family: var(--mono); font-size: 16px; font-weight: 700; color: var(--text-3); margin-left: -4px; }
.safe-max-btn { flex-shrink: 0; padding: 5px 10px; font-size: 12px; }

/* Slider risk zones */
.slider-zones {
Expand Down Expand Up @@ -1192,6 +1203,7 @@ main { flex: 1; max-width: 1200px; width: 100%; margin: 0 auto; padding: 20px 24
.swap-asset-select { width: 100%; }
.slider-row { flex-wrap: wrap; }
.leverage-num-input { width: 100%; margin-top: 4px; }
.safe-max-btn { width: 100%; justify-content: center; }
#wallet-area { flex-wrap: wrap; gap: 6px; }
.btn-connect { font-size: 12px; padding: 5px 10px; }
.slippage-opt { padding: 2px 6px; font-size: 11px; }
Expand Down