<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { storeToRefs } from 'pinia'
import { init, dispose } from 'klinecharts'
import { useFuturesStore, type KlineResolution, type Position, type ContractOrder, type HistoryPosition, type HistoryOrder } from '@/stores/futures'
import { useCoinsStore } from '@/stores/coins'
import { useAuthStore } from '@/stores/auth'
import { useMarketWs, type WsTradeItem, BAR_MS, BAR_COUNT } from '@/composables/useMarketWs'
import { get, post, postForm } from '@/utils/request'
import { useI18n } from 'vue-i18n'
import { normalizeTransferPair } from '@/stores/transfer'
import { walletTypeLabel } from '@/utils/walletTypeLabel'
import { extractUsdtAvailableFromSpotAssets } from '@/utils/spotAsset'
import { futuresPairToRouteParam } from '@/utils/futuresSymbol'
import AppModal from '@/components/common/AppModal.vue'

const route = useRoute()
const router = useRouter()
const { t, te, locale } = useI18n()
const store = useFuturesStore()
const coinsStore = useCoinsStore()
const authStore = useAuthStore()
const {
  symbol, displaySymbol, ticker, orderBook, klines, klineResolution,
  symbolInfo, positions, currentOrders, leverage, loading, orderLoading, error,
  allTickers, priceUp, spotAvailBalance, swapAvailBalance,
  historyPositions, historyOrders,
  fundingRecords,
  coinExtInfo, coinExtLoading,
  fundingRateCurrent, fundingRateHistory,
  marginMode, amountUnit, accountInfo, isSwitchingMode,
} = storeToRefs(store)
const { isLoggedIn } = storeToRefs(authStore)

// ── UI state ───────────────────────────────────────────
const subNavTab = ref<'chart' | 'coin-info' | 'funding-rate' | 'rules' | 'tiers'>('chart')
const bookTab = ref<'orderbook' | 'trades'>('orderbook')
const bookLayout = ref<'full' | 'asks' | 'bids'>('full')
const bookColRef = ref<HTMLElement | null>(null)
const bookTabsRef = ref<HTMLElement | null>(null)
const bookCtrlRef = ref<HTMLElement | null>(null)
const bookHeadRef = ref<HTMLElement | null>(null)
const bookMidRef = ref<HTMLElement | null>(null)
const bsRatioRef = ref<HTMLElement | null>(null)
const dynamicBookRows = ref(20)
const showPairSelector = ref(false)
const showLeveragePanel = ref(false)
const showMarginDrop = ref(false)
const showCondDrop = ref(false)
const showPrecDrop = ref(false)
const showAmountUnitDrop = ref(false)
const pairSearch = ref('')
const fundingCountdown = ref('00:00:00')

// ── 资金费率折线图 ──────────────────────────────────────
const frHoverIdx = ref(-1)
const frChartRef = ref<SVGSVGElement | null>(null)

// SVG 画布常量
const FR_W = 600, FR_H = 200
const FR_PL = 8, FR_PR = 62, FR_PT = 12, FR_PB = 24
const FR_PW = FR_W - FR_PL - FR_PR
const FR_PH = FR_H - FR_PT - FR_PB

function frTimeToMs(t: number | string | undefined): number {
  if (!t) return 0
  return typeof t === 'string' ? new Date(t).getTime() : Number(t)
}
function frFmtMMDD(ts: number): string {
  const d = new Date(ts)
  return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`
}
function frFmtDatetime(ts: number): string {
  const d = new Date(ts)
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`
}

const frItems = computed(() => {
  const cutoff = Date.now() - 7 * 24 * 3600 * 1000
  return fundingRateHistory.value
    .map(item => ({
      time: frTimeToMs(item.fundingTime),
      rate: Number(item.fundingRate ?? item.rate ?? 0),
    }))
    .filter(d => d.time > 0 && d.time >= cutoff)
    .sort((a, b) => a.time - b.time)
})

const frChart = computed(() => {
  const items = frItems.value
  if (items.length < 2) return null
  const rates = items.map(d => d.rate)
  const minR = Math.min(...rates)
  const maxR = Math.max(...rates)
  const rangePad = Math.max((maxR - minR) * 0.25, 0.000001)
  const yMin = minR - rangePad
  const yMax = maxR + rangePad
  const n = items.length
  const toX = (i: number) => FR_PL + (i / Math.max(n - 1, 1)) * FR_PW
  const toY = (r: number) => FR_PT + (1 - (r - yMin) / (yMax - yMin)) * FR_PH
  const pts = items.map((d, i) => ({ x: toX(i), y: toY(d.rate), time: d.time, rate: d.rate }))
  const linePath = pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ')
  const zeroY = toY(0)
  const baseY = Math.max(Math.min(zeroY, FR_PT + FR_PH), FR_PT)
  const areaPath = linePath + ` L${(FR_PL + FR_PW).toFixed(1)},${baseY.toFixed(1)} L${FR_PL.toFixed(1)},${baseY.toFixed(1)} Z`
  const showZero = zeroY > FR_PT && zeroY < FR_PT + FR_PH
  const xStep = Math.max(1, Math.floor(n / 5))
  const xLabels = pts.filter((_, i) => i % xStep === 0 || i === n - 1).map(p => ({ x: p.x, label: frFmtMMDD(p.time) }))
  const yLabels: { y: number; label: string }[] = []
  const hGrid: number[] = []
  for (let i = 0; i <= 4; i++) {
    const r = yMin + (yMax - yMin) * (1 - i / 4)
    const y = toY(r)
    if (y > FR_PT + 2 && y < FR_PT + FR_PH - 2) {
      yLabels.push({ y, label: (r * 100).toFixed(3) + '%' })
      hGrid.push(y)
    }
  }
  return { pts, linePath, areaPath, zeroY, showZero, xLabels, yLabels, hGrid }
})

// 优先用 HTTP 接口费率（预测下期），降级到 WS 实时值
const frCurrentRate = computed<number | null>(() => {
  const h = fundingRateCurrent.value
  if (h) {
    const v = Number(h.fundingRate ?? h.rate ?? NaN)
    if (!isNaN(v)) return v
  }
  return wsFundingRate.value ? parseFloat(wsFundingRate.value) : null
})
const frCurrentRateStr = computed(() => {
  const r = frCurrentRate.value
  if (r === null) return '--'
  return (r >= 0 ? '+' : '') + (r * 100).toFixed(4) + '%'
})

// 从历史数据的时间间隔推算结算周期（对齐 Flutter 的 '8H' 逻辑）
const frPeriodLabel = computed(() => {
  const items = frItems.value
  if (items.length < 2) return '8H'
  const diffs: number[] = []
  for (let i = 1; i < Math.min(items.length, 10); i++) {
    diffs.push(Math.round((items[i].time - items[i - 1].time) / 3600000))
  }
  const counts = new Map<number, number>()
  diffs.forEach(d => counts.set(d, (counts.get(d) || 0) + 1))
  let maxCount = 0, mode = 8
  counts.forEach((cnt, val) => { if (cnt > maxCount) { maxCount = cnt; mode = val } })
  return `${mode}H`
})

function onFrMouseMove(e: MouseEvent) {
  const svg = frChartRef.value
  const chart = frChart.value
  if (!svg || !chart) { frHoverIdx.value = -1; return }
  const rect = svg.getBoundingClientRect()
  const svgX = (e.clientX - rect.left) / rect.width * FR_W
  let best = 0, bestDist = Infinity
  chart.pts.forEach((p, i) => {
    const d = Math.abs(p.x - svgX)
    if (d < bestDist) { bestDist = d; best = i }
  })
  frHoverIdx.value = best
}
function onFrMouseLeave() { frHoverIdx.value = -1 }

// 列表高度跟踪：用 ResizeObserver 动态计算可见行数
const frListRef = ref<HTMLElement | null>(null)
const frListHeight = ref(300)
const FR_ROW_H = 35   // 每行高度（padding 8+8 + 字号12*行高1.3 + 1px边框）
const FR_HEAD_H = 31  // 表头高度

let frListRo: ResizeObserver | null = null
watch(frListRef, (el) => {
  frListRo?.disconnect()
  if (el) {
    frListRo = new ResizeObserver(() => { frListHeight.value = el.clientHeight })
    frListRo.observe(el)
    frListHeight.value = el.clientHeight
  }
})

const frVisibleItems = computed(() => {
  const count = Math.max(1, Math.floor((frListHeight.value - FR_HEAD_H) / FR_ROW_H))
  return [...frItems.value].reverse().slice(0, count)
})

// ── WebSocket real-time data ────────────────────────────
const wsMarkPrice = ref('')
const wsFundingRate = ref('')
const wsFundingNextTime = ref(0)
const recentTrades = ref<WsTradeItem[]>([])
const rawBookAsks = ref<[number, number][]>([])
const rawBookBids = ref<[number, number][]>([])
const klineRealtimeClose = ref<number | null>(null)
let countdownTimer: ReturnType<typeof setInterval> | null = null
// 逐笔价格方向：当前价格 vs 上一次价格
const tickPriceUp = ref(true)
let prevTickClose = 0
// klinecharts v10 real-time update callback (set via subscribeBar)
let klineBarCallback: ((bar: any) => void) | null = null
let bookRo: ResizeObserver | null = null

function updateCountdown() {
  if (!wsFundingNextTime.value) return
  const diff = wsFundingNextTime.value - Date.now()
  if (diff <= 0) { fundingCountdown.value = '00:00:00'; return }
  const h = Math.floor(diff / 3_600_000)
  const m = Math.floor((diff % 3_600_000) / 60_000)
  const s = Math.floor((diff % 60_000) / 1000)
  fundingCountdown.value = `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`
}

const ws = useMarketWs({
  onConnected() {
    ws.requestKlines()
  },
  onKlineHistory(bars) {
    if (!bars.length || !chart) return
    currentBars = bars
    const period = RESOLUTION_PERIOD[klineResolution.value] ?? { type: 'minute', span: 1 }
    if (JSON.stringify(chart.getPeriod()) !== JSON.stringify(period)) {
      chart.setPeriod(period)
    } else {
      chart.resetData()
    }
  },
  onKline(bar) {
    // 与图表蜡烛方向同步：当前周期 close >= open 则绿，否则红
    tickPriceUp.value = bar.close >= bar.open
    klineRealtimeClose.value = bar.close
    klineBarCallback?.({
      timestamp: bar.timestamp,
      open: bar.open, high: bar.high, low: bar.low, close: bar.close, volume: bar.volume,
    })
  },
  onTicker(data) {
    const closeN = parseFloat(data.close)
    const openN = parseFloat(data.open)
    // kline 未到达前用 24h open/close 初始化方向
    if (prevTickClose === 0) tickPriceUp.value = closeN >= openN
    prevTickClose = closeN
    const chg = openN > 0 ? String((closeN - openN) / openN) : (ticker.value?.chg ?? '0')
    const change = openN > 0 ? String(closeN - openN) : (ticker.value?.change ?? '0')
    ticker.value = {
      symbol: symbol.value,
      close: data.close,
      open: data.open,
      high: data.high,
      low: data.low,
      volume: data.volume,
      turnover: data.turnover,
      chg,
      change,
	  stockType: data.stockType
    }
  },
  onMark(data) {
    wsMarkPrice.value = data.markPrice
    wsFundingRate.value = data.fundingRate
    wsFundingNextTime.value = data.nextFundingTime
    updateCountdown()
  },
  onTrade(item) {
    recentTrades.value.unshift(item)
    if (recentTrades.value.length > 40) recentTrades.value.length = 40
  },
  onKlineHistoryMore(bars) {
    const cb = loadMoreCallback
    loadMoreCallback = null
    if (!cb) return
    if (!bars.length) {
      cb([], false) // 服务端没有更早数据，more=false 告知图表停止请求
      return
    }
    const firstTs = currentBars[0]?.timestamp ?? Infinity
    const newBars = bars
      .filter(b => b.timestamp < firstTs)
      .sort((a, b) => a.timestamp - b.timestamp)
    if (newBars.length > 0) {
      currentBars = [...newBars, ...currentBars]
    }
    // 仅传 newBars，klinecharts forward 回调会将它们 prepend 到现有数据前面
    // more=true 继续允许加载更早数据，more=false 表示没有更早数据了
    cb(newBars, newBars.length > 0)
  },
  onBook(data) {
    const toPair = (rows: [string, string][]) =>
      rows
        .map(([p, q]) => [parseFloat(p), parseFloat(q)] as [number, number])
        .filter(([p, q]) => p > 0 && q > 0 && Number.isFinite(p) && Number.isFinite(q))
    rawBookAsks.value = toPair(data.asks).sort((a, b) => a[0] - b[0])
    rawBookBids.value = toPair(data.bids).sort((a, b) => b[0] - a[0])

    const addTotal = (rows: [string, string][]) => {
      let acc = 0
      return rows.map(([price, qty]) => {
        acc += parseFloat(qty)
        return { price, amount: qty, total: String(acc) }
      })
    }
    orderBook.value = {
      ask: { items: addTotal(data.asks) },
      bid: { items: addTotal(data.bids) },
    }
  },
})

// ── Chart ──────────────────────────────────────────────
const chartEl = ref<HTMLDivElement | null>(null)
let chart: ReturnType<typeof init> | null = null
let ro: ResizeObserver | null = null
// 向左滚动加载更多历史数据的待回调
let loadMoreCallback: ((bars: any[], noMore: boolean) => void) | null = null

const showMA = ref(true)
const showVol = ref(true)
const crosshairData = ref<any>(null)
const activeDrawTool = ref('cursor')
const chartWrapWidth = ref(0)

const tooltipStyle = computed(() => {
  const x = crosshairData.value?.x ?? 0
  if (x > chartWrapWidth.value / 2) {
    return { left: '4px', right: 'auto' }
  }
  return { left: 'auto', right: '4px' }
})

const TF_OPTIONS = computed<{ label: string; value: KlineResolution }[]>(() => [
  { label: t('futuresPage.tf.m1'), value: '1' },
  { label: t('futuresPage.tf.m5'), value: '5' },
  { label: t('futuresPage.tf.m15'), value: '15' },
  { label: t('futuresPage.tf.m30'), value: '30' },
  { label: t('futuresPage.tf.h1'), value: '60' },
  { label: t('futuresPage.tf.h4'), value: '240' },
  { label: t('futuresPage.tf.d1'), value: '1D' },
  { label: t('futuresPage.tf.w1'), value: '1W' },
  { label: t('futuresPage.tf.mo1'), value: '1M' },
  { label: t('futuresPage.tf.mo3'), value: '3M' },
])

const DRAW_TOOLS: Record<string, string> = {
  trend:   'segment',
  hline:   'horizontalStraightLine',
  ray:     'rayLine',
  channel: 'priceChannelLine',
  fib:     'fibonacciLine',
  text:    'simpleAnnotation',
}

let currentBars: any[] = []

const RESOLUTION_PERIOD: Record<string, { type: 'minute' | 'hour' | 'day'; span: number }> = {
  '1':   { type: 'minute', span: 1  },
  '5':   { type: 'minute', span: 5  },
  '15':  { type: 'minute', span: 15 },
  '30':  { type: 'minute', span: 30 },
  '60':  { type: 'hour',   span: 1  },
  '240': { type: 'hour',   span: 4  },
  '1D':  { type: 'day',    span: 1  },
}

function convertKlines() {
  const seen = new Set<number>()
  return klines.value
    .map(b => ({
      timestamp: b.time > 1e10 ? b.time : b.time * 1000,
      open:  parseFloat(String(b.open)),
      high:  parseFloat(String(b.high)),
      low:   parseFloat(String(b.low)),
      close: parseFloat(String(b.close)),
      volume: b.volume > 0 ? b.volume : Math.max(0.01, parseFloat(ticker.value?.volume || '1') / 200),
    }))
    .sort((a, b) => a.timestamp - b.timestamp)
    .filter(b => { if (seen.has(b.timestamp)) return false; seen.add(b.timestamp); return true })
}

function destroyChart() {
  ro?.disconnect(); ro = null
  if (chartEl.value) dispose(chartEl.value)
  chart = null; currentBars = []; klineBarCallback = null; loadMoreCallback = null
}

function initChart() {
  if (!chartEl.value) return
  chart = init(chartEl.value, {
    locale: locale.value.startsWith('zh') ? 'zh-CN' : 'en-US',
    timezone: 'Asia/Shanghai',
    styles: {
      grid: {
        horizontal: { color: 'rgba(255,255,255,0.04)', size: 1 },
        vertical:   { color: 'rgba(255,255,255,0.04)', size: 1 },
      },
      candle: {
        type: 'candle_solid',
        bar: {
          upColor: '#02c076', downColor: '#f6465d', noChangeColor: '#888',
          upBorderColor: '#02c076', downBorderColor: '#f6465d', noChangeBorderColor: '#888',
          upWickColor: '#02c076', downWickColor: '#f6465d', noChangeWickColor: '#888',
        },
        priceMark: {
          high: { show: false },
          low:  { show: false },
          last: {
            show: true,
            upColor: '#02c076', downColor: '#f6465d', noChangeColor: '#888',
            line: { show: true, style: 'dashed', dashedValue: [4, 4], size: 1 },
            text: { show: true, size: 11 },
          },
        },
        tooltip: { showRule: 'none', showType: 'standard' },
      },
      crosshair: {
        show: true,
        horizontal: {
          show: true,
          line: { show: true, style: 'dashed', dashedValue: [4, 2], size: 1, color: 'rgba(255,255,255,0.2)' },
          text: { show: true, size: 11, color: '#d1d1d2', backgroundColor: '#1d1d1d' },
        },
        vertical: {
          show: true,
          line: { show: true, style: 'dashed', dashedValue: [4, 2], size: 1, color: 'rgba(255,255,255,0.2)' },
          text: { show: true, size: 11, color: '#d1d1d2', backgroundColor: '#1d1d1d' },
        },
      },
      xAxis: {
        axisLine: { show: true, color: 'rgba(255,255,255,0.07)', size: 1 },
        tickText: { show: true, color: '#6a7182', size: 11 },
        tickLine: { show: true, color: 'rgba(255,255,255,0.07)', size: 1, length: 3 },
      },
      yAxis: {
        axisLine: { show: true, color: 'rgba(255,255,255,0.07)', size: 1 },
        tickText: { show: true, color: '#6a7182', size: 11 },
        tickLine: { show: true, color: 'rgba(255,255,255,0.07)', size: 1, length: 3 },
      },
      separator: { color: '#2a2a2a', size: 3 },
      indicator: { tooltip: { showRule: 'none' } },
      overlay: {
        point: {
          color: '#f0b90b', borderColor: 'rgba(240,185,11,0.35)',
          activeColor: '#f0b90b', activeBorderColor: 'rgba(240,185,11,0.6)',
          borderSize: 1, radius: 4, activeBorderSize: 2, activeRadius: 5,
        },
        line: { color: '#f0b90b', size: 1, style: 'solid' },
        text: { color: '#f0b90b', size: 12 },
      },
    },
  })
  if (!chart) return

  if (showMA.value) {
    chart.createIndicator({
      name: 'MA',
      calcParams: [5, 10, 30],
      styles: { lines: [{ color: '#f0b90b', size: 1 }, { color: '#5d9cf5', size: 1 }, { color: '#e84d5b', size: 1 }] },
    }, { isStack: false, pane: { id: 'candle_pane' } } as any)
  }
  if (showVol.value) {

    chart.createIndicator(
      { name: 'VOL', calcParams: [], styles: {
        bars: [{ style: 'fill' }, { style: 'fill' }],
        lines: [{ size: 0, color: 'rgba(0,0,0,0)' }, { size: 0, color: 'rgba(0,0,0,0)' }, { size: 0, color: 'rgba(0,0,0,0)' }],
        lastValueMark: { show: false },
        tooltip: { showRule: 'none' },
      }},
      { isStack: false, pane: { id: 'vol-pane', height: 80, minHeight: 40 }} as any,
    )
  }

  chart.setSymbol({
    name: displaySymbolNoDash.value,
    pricePrecision: futuresPriceDecimals(),
    volumePrecision: 4,
  })
  chart.setPeriod(RESOLUTION_PERIOD[klineResolution.value] ?? { type: 'minute', span: 1 })
  currentBars = convertKlines()
  chart.setDataLoader({
    getBars: (params: any) => {
      if (params.type === 'init') {
        // 初始加载：backward=false（实时由subscribeBar处理），forward=true（可向左请求更多历史）
        params.callback(currentBars, { backward: false, forward: true })
        return
      }
      if (params.type === 'backward') {
        // 用户滚到右侧最新位置，subscribeBar 已处理实时，这里不重复
        params.callback([], false)
        return
      }
      // type === 'forward'：用户滚到左侧最早位置，params.timestamp 是最旧 bar 的时间戳
      if (!params.timestamp) {
        params.callback([], false)
        return
      }
      const to = params.timestamp as number
      const from = to - (BAR_MS[klineResolution.value] ?? BAR_MS['60']) * BAR_COUNT
      loadMoreCallback = params.callback
      ws.requestKlinesRange(from, to)
    },
    subscribeBar: (params: any) => { klineBarCallback = params.callback },
    unsubscribeBar: () => { klineBarCallback = null },
  })
  chart.subscribeAction('onCrosshairChange', (data: any) => {
    if (!data?.x || !data?.paneId) { crosshairData.value = null; return }
    // v10-beta bug: kLineData is not passed to the action callback, retrieve it manually
    const pts = (chart as any).convertFromPixel([{ x: data.x, y: data.y ?? 0 }])
    const ts: number | undefined = Array.isArray(pts) ? pts[0]?.timestamp : (pts as any)?.timestamp
    if (!ts) { crosshairData.value = null; return }
    const dataList: any[] = (chart as any).getDataList?.() ?? []
    const kLineData = dataList.find((b: any) => b.timestamp === ts)
    crosshairData.value = kLineData ? { ...data, kLineData } : null
  })
  ro = new ResizeObserver(() => {
    chart?.resize()
    chartWrapWidth.value = chartEl.value?.offsetWidth ?? 0
  })
  ro.observe(chartEl.value!)
  chartWrapWidth.value = chartEl.value.offsetWidth
  // 限制向右最多留 2 根 K 线的空白（约 20px），防止用户拖出当前时间太远
  chart.setMaxOffsetRightDistance(20)
}

function updateChart() {
  if (!chart || !klines.value.length) return
  currentBars = convertKlines()
  const period = RESOLUTION_PERIOD[klineResolution.value] ?? { type: 'minute', span: 1 }
  if (JSON.stringify(chart.getPeriod()) !== JSON.stringify(period)) {
    chart.setPeriod(period)
  } else {
    chart.resetData()
  }
}


function clearOverlays() { chart?.removeOverlay() }
function zoomOut() { (chart as any)?.zoomAtCoordinate?.(0.8) }

function activateDrawTool(tool: string) {
  activeDrawTool.value = tool
  if (!chart || tool === 'cursor') return
  const name = DRAW_TOOLS[tool]
  if (name) chart.createOverlay({ name })
}

watch(klines, updateChart)
watch(showMA, (show) => {
  if (!chart) return
  if (show) {
    chart.createIndicator({ name: 'MA', calcParams: [5, 10, 30], styles: { lines: [{ color: '#f0b90b', size: 1 }, { color: '#5d9cf5', size: 1 }, { color: '#e84d5b', size: 1 }] } }, { isStack: true, pane: { id: 'candle_pane' } } as any)
  } else {
    chart.removeIndicator({ name: 'MA', paneId: 'candle_pane' })
  }
})
watch(showVol, (show) => {
  if (!chart) return
  if (show) {

    chart.createIndicator(
      { name: 'VOL', calcParams: [], styles: {
        bars: [{ style: 'fill' }, { style: 'fill' }],
        lines: [{ size: 0, color: 'rgba(0,0,0,0)' }, { size: 0, color: 'rgba(0,0,0,0)' }, { size: 0, color: 'rgba(0,0,0,0)' }],
        lastValueMark: { show: false },
        tooltip: { showRule: 'none' },
      }},
      { isStack: false, pane: { id: 'vol-pane', height: 80, minHeight: 40 } } as any,
    )
  } else {
    chart.removeIndicator({ name: 'VOL', paneId: 'vol-pane' })
  }
})

// ── 搜索框input selector ──────────────────────────────────────
const filteredTickers = computed(() => {
  const q = pairSearch.value.toLowerCase()
  // console.log("搜索列表数据：",allTickers.value)
  return allTickers.value.filter(t => t.symbol.toLowerCase().includes(q))
})

// 搜索标签：股票类型' 1.加密货币，2.大宗商品 3.贵金属 4.外汇
const serchTagItems = computed(() => {
  return [
    { key: 'futuresPage.pairSearchTag.oneType', value: 1 },
    { key: 'futuresPage.pairSearchTag.twoType', value: 2 },
    { key: 'futuresPage.pairSearchTag.threeType', value: 3 },
    { key: 'futuresPage.pairSearchTag.fourType', value: 4 },
  ]
})

function coinIcon(sym: string): string {
  return coinsStore.coins.find(c => c.symbol === sym)?.icon ?? ''
}

const currentCoinIcon = computed(() => coinIcon(symbol.value))

function selectPair(sym: string) {
  showPairSelector.value = false
  pairSearch.value = ''
  const routeSym = futuresPairToRouteParam(sym)
  router.push({ name: 'futures', params: { symbol: routeSym } })
}

// ── Trading panel ──────────────────────────────────────
const side = ref<'buy' | 'sell'>('buy')
const orderType = ref<'limit' | 'market' | 'conditional'>('limit')
const conditionalSubType = ref<'market' | 'limit'>('market')
const entrustPrice = ref('')
const triggerPrice = ref('')
const volume = ref('')
const pctSlider = ref(0)
const activeTab = ref<'positions' | 'orders' | 'history-positions' | 'history-orders' | 'trades' | 'funding' | 'assets'>('positions')

/**
 * 限价委托价：用全市场行情表里当前币对的 close。
 * 切换币对时合约 WS 的 ticker 往往还是上一币对的快照，不能直接用 ticker.close 回填。
 */
function syncEntrustPriceFromCoinList(): boolean {
  const sym = symbol.value
  const row = allTickers.value.find((x) => x.symbol === sym)
  if (row?.close && parseFloat(row.close) > 0) {
    entrustPrice.value = row.close
    return true
  }
  return false
}

// ── 底部标签页无限滚动 ──────────────────────────────────
const tabScrollRef = ref<HTMLElement | null>(null)
let tabScrollLoading = false
function onTabScroll() {
  const el = tabScrollRef.value
  if (!el || tabScrollLoading) return
  if (el.scrollHeight - el.scrollTop - el.clientHeight < 100) {
    tabScrollLoading = true
    const tab = activeTab.value
    let promise: Promise<void> | undefined
    if (tab === 'history-positions') promise = store.fetchHistoryPositions()
    else if (tab === 'history-orders') promise = store.fetchHistoryOrders()
    else if (tab === 'trades') promise = store.fetchHistoryOrders()
    else if (tab === 'funding') promise = store.fetchFundingRecords()
    ;(promise ?? Promise.resolve()).finally(() => { tabScrollLoading = false })
  }
}

// ── 轮询：登录时每 3 秒刷新持仓和当前委托 ──────────────
let pollTimer: ReturnType<typeof setInterval> | null = null
function startPolling() {
  if (pollTimer !== null) return
  pollTimer = setInterval(() => {
    store.fetchPositions()
    store.fetchCurrentOrders()
  }, 3000)
}
function stopPolling() {
  if (pollTimer !== null) { clearInterval(pollTimer); pollTimer = null }
}

// ── Mobile ──────────────────────────────────────────────
const isMobile = ref(typeof window !== 'undefined' && window.innerWidth <= 768)
const mobileTab = ref<'chart' | 'trade'>('chart')
function onWindowResize() {
  isMobile.value = window.innerWidth <= 768
  recalcBookRowCount()
}

// ── Transfer modal ──────────────────────────────────────
// 资金 ↔ 合约 / 跟单（与资产页、transfer store 一致）
type AccountType = 'SPOT' | 'SWAP' | 'FOLLOW' | 'SPOT_TRADING'
const TRANSFER_FOLLOW_IDX = 1

/** 合约页划转可选的非资金账户（不含 iBit 质押） */
const FUTURES_OTHER_WALLET_TYPES: Exclude<AccountType, 'SPOT'>[] = ['SWAP', 'FOLLOW', 'SPOT_TRADING']

function transferWalletLabel(kind: AccountType): string {
  return walletTypeLabel(t, kind)
}

const showTransfer = ref(false)
const transferFrom = ref<AccountType>('SPOT')
const transferTo = ref<AccountType>('SWAP')
const transferAmount = ref('')
const transferAvail = ref('0.00000000')
const transferLoading = ref(false)
const transferSubmitting = ref(false)
const transferError = ref('')
const showFromDrop = ref(false)
const showToDrop = ref(false)


function selectFromOther(v: AccountType) {
  transferFrom.value = v
  transferTo.value = 'SPOT'
  showFromDrop.value = false
  transferAmount.value = ''
  fetchTransferAvail()
}

function selectToOther(v: AccountType) {
  transferTo.value = v
  transferFrom.value = 'SPOT'
  showToDrop.value = false
  transferAmount.value = ''
  fetchTransferAvail()
}

function swapTransferDirection() {
  const [a, b] = normalizeTransferPair(transferTo.value, transferFrom.value)
  transferFrom.value = a
  transferTo.value = b
  transferAmount.value = ''
  showFromDrop.value = false
  showToDrop.value = false
  fetchTransferAvail()
}

// 统一用 /swap/wallet-new/get（含 SPOT / SWAP / 跟单 accountInfoList）；现货账户单独请求
async function fetchTransferAvail() {
  if (transferFrom.value === 'SPOT') {
    transferAvail.value = spotAvailBalance.value
  } else if (transferFrom.value === 'SWAP') {
    transferAvail.value = swapAvailBalance.value
  } else {
    transferAvail.value = '0'
  }
  transferLoading.value = true
  try {
    const data = await get<any>('/swap/wallet-new/get')
    if (transferFrom.value === 'SPOT') {
      transferAvail.value = data?.spotWallet?.balance ?? spotAvailBalance.value
    } else if (transferFrom.value === 'SWAP') {
      transferAvail.value = data?.availableBalance ?? swapAvailBalance.value
    } else if (transferFrom.value === 'SPOT_TRADING') {
      try {
        const raw = await get<unknown>('spot/v1/account/asset/list')
        transferAvail.value = extractUsdtAvailableFromSpotAssets(raw)
      } catch {
        transferAvail.value = '0'
      }
    } else {
      transferAvail.value = data?.accountInfoList?.[TRANSFER_FOLLOW_IDX]?.balance ?? '0'
    }
  } catch (e) {
    console.error('[transfer] fetchTransferAvail error:', e)
  } finally {
    transferLoading.value = false
  }
}

watch(showTransfer, (v) => {
  if (v) {
    transferAmount.value = ''
    transferError.value = ''
    fetchTransferAvail()
  }
})

async function confirmTransfer() {
  const [fromWallet, toWallet] = normalizeTransferPair(transferFrom.value, transferTo.value)
  transferFrom.value = fromWallet
  transferTo.value = toWallet
  const amt = parseFloat(transferAmount.value)
  if (!transferAmount.value || isNaN(amt) || amt <= 0) {
    transferError.value = t('spot.transferModal.invalidAmount')
    return
  }
  const avail = parseFloat(transferAvail.value)
  if (amt > avail) {
    transferError.value = t('spot.transferModal.insufficientBalance')
    return
  }
  transferSubmitting.value = true
  transferError.value = ''
  try {
    if (fromWallet === 'SPOT_TRADING' || toWallet === 'SPOT_TRADING') {
      const direction = fromWallet === 'SPOT' ? 1 : 2
      await post('/spot/v1/account/transfer', {
        symbol: 'USDT',
        amount: amt,
        direction,
      })
    } else {
      await postForm('/uc/asset/transfer', {
        amount: transferAmount.value,
        from: fromWallet,
        to: toWallet,
      })
    }
    transferAmount.value = ''
    showTransfer.value = false
    // 刷新合约钱包余额
    store.fetchPositions?.().catch(() => {})
  } catch (e: any) {
    transferError.value = e?.message ?? t('spot.transferModal.transferFailed')
  } finally {
    transferSubmitting.value = false
  }
}

// ── TP/SL modify modal ──────────────────────────────────
const showTpSlModal = ref(false)
const tpSlPositionId = ref('')
const tpSlProfitPrice = ref('')
const tpSlLossPrice = ref('')
const tpSlError = ref('')

// 当前正在编辑的仓位（从 positions 中反查）
const tpSlPosition = computed(() => positions.value.find(p => p.id === tpSlPositionId.value) ?? null)

/** 止盈止损弹窗：仅当前页合约与仓位一致时使用 WS 标记价，否则用仓位接口自带的标记价 */
const tpSlMarkPriceStr = computed(() => {
  const pos = tpSlPosition.value
  if (!pos) return ''
  const posSym = pos.coin?.symbol ?? ''
  const samePair = posSym.length > 0 && futuresPairKey(posSym) === futuresPairKey(symbol.value)
  if (samePair && wsMarkPrice.value) {
    return wsMarkPrice.value
  }
  return pos.currentPrice ?? ''
})

function openTpSlModal(positionId: string, fallbackProfit?: string, fallbackLoss?: string) {
  tpSlPositionId.value = positionId
  const pos = positions.value.find(p => p.id === positionId)
  const cut0 = pos?.cutList?.[0]
  // 优先从仓位 cutList 读；没有则用 fallback（从委托行传入）
  tpSlProfitPrice.value = (cut0?.profitPrice && parseFloat(String(cut0.profitPrice)) > 0)
    ? String(cut0.profitPrice)
    : (fallbackProfit && parseFloat(fallbackProfit) > 0 ? fallbackProfit : '')
  tpSlLossPrice.value = (cut0?.lossPrice && parseFloat(String(cut0.lossPrice)) > 0)
    ? String(cut0.lossPrice)
    : (fallbackLoss && parseFloat(fallbackLoss) > 0 ? fallbackLoss : '')
  tpSlError.value = ''
  showTpSlModal.value = true
}

async function confirmTpSl() {
  tpSlError.value = ''
  const ok = await store.modifyTpSl(tpSlPositionId.value, tpSlProfitPrice.value, tpSlLossPrice.value)
  if (ok) {
    showTpSlModal.value = false
  } else {
    tpSlError.value = store.error || t('futuresPage.errors.modifyTpSlFailed')
  }
}


// ── 详情弹窗 ────────────────────────────────────────────
const showPosDetail = ref(false)
const showOrdDetail = ref(false)
const showHistPosDetail = ref(false)
const showHistOrdDetail = ref(false)

const detailPos = ref<Position | null>(null)
const detailOrd = ref<ContractOrder | null>(null)
const detailHistPos = ref<HistoryPosition | null>(null)
const detailHistOrd = ref<HistoryOrder | null>(null)

function openPosDetail(pos: Position) { detailPos.value = pos; showPosDetail.value = true }
function openOrdDetail(ord: ContractOrder) { detailOrd.value = ord; showOrdDetail.value = true }
function openHistPosDetail(pos: HistoryPosition) { detailHistPos.value = pos; showHistPosDetail.value = true }
function openHistOrdDetail(ord: HistoryOrder) { detailHistOrd.value = ord; showHistOrdDetail.value = true }

// 仓位类型标签（全仓/分仓/体验金）
function posTypeLabel(positionType: any, patterns: any, experienceRecordId?: any): string {
  if (experienceRecordId && String(experienceRecordId) !== 'null' && String(experienceRecordId) !== '') {
    return t('futuresPage.margin.experience')
  }
  const pt = String(positionType ?? '').toUpperCase()
  if (pt === '0' || pt === 'INTEGRAL') {
    return t('futuresPage.margin.cross')
  }
  if (pt === '1' || pt === 'SEPARATE') {
    return t('futuresPage.margin.split')
  }
  const p = String(patterns ?? '').toUpperCase()
  if (p === 'CROSSED') {
    return t('futuresPage.margin.cross')
  }
  if (p === 'ISOLATED' || p === 'SEPARATE') {
    return t('futuresPage.margin.split')
  }
  return t('futuresPage.margin.cross')
}

// 历史仓位符号：兼容嵌套 coin 对象和平铺字段
function histPosSymbol(pos: HistoryPosition): string {
  const s = (pos.coin?.symbol ?? pos.symbol ?? pos.coinSymbol ?? '').replace('/', '-')
  return s || '--'
}

// 历史委托符号
function histOrdSymbol(ord: HistoryOrder): string {
  return (ord.symbol ?? ord.coinSymbol ?? '').replace('/', '-') || '--'
}

// 历史仓位开仓均价
function histPosOpenPrice(pos: HistoryPosition): string {
  return fmtPrice(pos.openPrice ?? pos.usdtOpenPrice ?? '0')
}

// 历史仓位平仓均价
function histPosClosePrice(pos: HistoryPosition): string {
  const p = pos.closePrice ?? pos.tradedPrice ?? '0'
  return parseFloat(p) > 0 ? fmtPrice(p) : '--'
}

// 历史仓位委托总量
function histPosTotalVol(pos: HistoryPosition): string {
  return String(pos.totalPosition ?? pos.volume ?? pos.tradedVolume ?? '--')
}

// 历史仓位已成交量
function histPosDealVol(pos: HistoryPosition): string {
  return String(pos.closedPosition ?? pos.tradedVolume ?? pos.dealVolume ?? '--')
}

// 历史仓位盈亏
function histPosProfit(pos: HistoryPosition): number {
  return parseFloat(String(pos.usdtProfit ?? pos.profitAndLoss ?? '0'))
}

// 历史仓位保证金
function histPosPrincipal(pos: HistoryPosition): number {
  return parseFloat(String(pos.totalPrincipalAmount ?? pos.principalAmount ?? '0'))
}

// 历史仓位开仓时间
function histPosOpenTime(pos: HistoryPosition): string {
  const t = pos.openTime ?? pos.usdtOpenTime ?? pos.createTime
  return t ? fmtTime(t) : '--'
}

// 历史仓位平仓时间
function histPosCloseTime(pos: HistoryPosition): string {
  const t = pos.closeTime ?? pos.dealTime
  return t ? fmtTime(t) : '--'
}

// 历史仓位收益率
function histPosProfitRate(pos: HistoryPosition): string {
  const pnl = histPosProfit(pos)
  const margin = histPosPrincipal(pos)
  if (!margin) return '--'
  const r = pnl / margin * 100
  return (r >= 0 ? '+' : '') + r.toFixed(2) + '%'
}

// 历史委托开仓均价（开仓单用 tradedPrice；平仓单用 usdtOpenPrice）
function histOrdAvgOpenPrice(ord: HistoryOrder): string {
  const isClose = String(ord.entrustType ?? '').toUpperCase() === 'CLOSE' || String(ord.entrustType) === '1'
  const p = isClose ? (ord.usdtOpenPrice ?? ord.openPrice ?? '0') : (ord.tradedPrice ?? '0')
  return parseFloat(p) > 0 ? fmtPrice(p) : '--'
}

// 历史委托委托价格（市价→"市价"；限价/计划→显示价格）
function histOrdEntrustPriceDisplay(ord: HistoryOrder): string {
  return histOrdEntrustPrice(ord)
}

// 基础货币（BTC/ETH 等）
function baseCoinFromSym(sym: string): string {
  if (sym.includes('/')) return sym.split('/')[0]
  return sym.replace(/USDT$|BUSD$/, '') || sym
}

function txTypeName(ty: number) {
  const key = `futuresPage.fundingTx.${ty}`
  if (te(key)) {
    return t(key)
  }
  return t('futuresPage.errors.txFallback', { n: ty })
}

/** 资金流水币种：后端 symbol 含「永续」，按七种语言展示 */
function formatFundingSymbol(sym?: string): string {
  const raw = (sym || 'USDT').trim()
  return raw.replace(/永续/g, t('futuresPage.perpetualTag'))
}
// 合约页资金流水：前端兜底移除现货相关类型
const SPOT_RELATED_TX_TYPES = new Set<number>([3, 43, 44, 45, 46, 48])
const contractFundingRecords = computed(() =>
  fundingRecords.value.filter(rec => !SPOT_RELATED_TX_TYPES.has(Number(rec.type))),
)
const baseCurrency = computed(() => symbol.value.split('/')[0] ?? 'BTC')
const quoteCurrency = computed(() => symbol.value.split('/')[1] ?? 'USDT')

/** 合约代码规范化比对（BTC/USDT、BTC-USDT → BTCUSDT） */
function futuresPairKey(sym: string): string {
  return sym.replace(/\//g, '').replace(/-/g, '').trim().toUpperCase()
}

const displaySymbolNoDash = computed(() => symbol.value.replace('/', ''))

async function submitOrder() {
  if (!volume.value) return
  const baseVol = toBaseVolume(volume.value)
  if (!baseVol) {
    store.error = orderRefPrice() <= 0
      ? (orderType.value === 'conditional' && conditionalSubType.value === 'market'
        ? t('futuresPage.errors.enterTriggerPrice')
        : orderType.value !== 'market' ? t('futuresPage.errors.enterPrice') : t('futuresPage.errors.priceNotReadyConvert'))
      : t('futuresPage.errors.invalidQtyCheckMin')
    return
  }
  // type: 0=市价 1=限价 2=计划委托（与 Android OpenEntrustType 一致）
  let type: 0 | 1 | 2 = 1
  let ep: string | undefined
  let tp: string | undefined
  if (orderType.value === 'limit') {
    type = 1; ep = entrustPrice.value
  } else if (orderType.value === 'market') {
    type = 0
  } else {
    // 条件委托：触发价必填
    if (!triggerPrice.value) {
      store.error = t('futuresPage.errors.enterTriggerPrice')
      return
    }
    tp = triggerPrice.value
    if (conditionalSubType.value === 'limit') {
      type = 2; ep = entrustPrice.value
    } else {
      // 市价委托：entrustPrice = triggerPrice（后端要求必填，触发时以该价格挂限价单）
      type = 2; ep = triggerPrice.value
    }
  }
  const ok = await store.placeOrder({
    direction: side.value === 'buy' ? 0 : 1,  // 0=开多 1=开空
    type,
    entrustPrice: ep,
    triggerPrice: tp,
    volume: baseVol,
  })
  if (ok) { volume.value = ''; triggerPrice.value = '' }
}

function setLatestPrice() {
  if (ticker.value) entrustPrice.value = ticker.value.close
}

// 订单簿点击 → 回填到当前编辑的价格输入框：
//   - 平仓 sheet 打开时，回填到平仓价
//   - 否则若选中的是限价/条件限价，回填到 entrustPrice；条件市价回填到触发价
function onBookClick(price: string) {
  if (showCloseSheet.value && !closeIsMarket.value) {
    closeSheetPrice.value = price
    return
  }
  if (orderType.value === 'limit') {
    entrustPrice.value = price
  } else if (orderType.value === 'conditional') {
    if (conditionalSubType.value === 'limit') entrustPrice.value = price
    else triggerPrice.value = price
  }
}

// ── Close position sheet (从仓位卡片调起) ──────────────────
const showCloseSheet = ref(false)
const closeTargetPos = ref<import('@/stores/futures').Position | null>(null)
const closeIsMarket = ref(true)
const closeSheetPrice = ref('')
const closeSheetVolume = ref('')
const closeSheetPct = ref(0)
const closeSheetError = ref('')

function openCloseSheet(pos: import('@/stores/futures').Position) {
  closeTargetPos.value = pos
  closeIsMarket.value = true
  const posSym = pos.coin?.symbol ?? ''
  const samePair = posSym.length > 0 && futuresPairKey(posSym) === futuresPairKey(symbol.value)
  closeSheetPrice.value = samePair ? (ticker.value?.close ?? pos.currentPrice ?? '') : (pos.currentPrice ?? '')
  closeSheetVolume.value = ''
  closeSheetPct.value = 0
  closeSheetError.value = ''
  showCloseSheet.value = true
}

watch(closeSheetPct, (pct) => {
  if (!closeTargetPos.value) return
  const avail = parseFloat(closeTargetPos.value.availablePosition) || 0
  if (!avail || pct === 0) { closeSheetVolume.value = ''; return }
  closeSheetVolume.value = ((pct / 100) * avail).toFixed(4)
})

/** 平仓弹窗展示的 base/quote 以目标仓位为准，避免多仓时沿用当前 K 线交易对 */
const closeSheetBaseCurrency = computed(() => {
  const raw = closeTargetPos.value?.coin?.symbol ?? ''
  const b = raw.split('/')[0]?.trim()
  return b || baseCurrency.value
})
const closeSheetQuoteCurrency = computed(() => {
  const raw = closeTargetPos.value?.coin?.symbol ?? ''
  const q = raw.split('/')[1]?.trim()
  return q || quoteCurrency.value
})

/** 平仓弹窗标记价：仅 WS 订阅与待平仓位合约一致时使用 wsMarkPrice，否则用仓位 currentPrice */
const closeSheetMarkPriceStr = computed(() => {
  const pos = closeTargetPos.value
  if (!pos) return ''
  const posSym = pos.coin?.symbol ?? ''
  const samePair = posSym.length > 0 && futuresPairKey(posSym) === futuresPairKey(symbol.value)
  if (samePair && wsMarkPrice.value) {
    return wsMarkPrice.value
  }
  return pos.currentPrice ?? ''
})

const closeSheetEstPnl = computed(() => {
  const pos = closeTargetPos.value
  if (!pos) return 0
  const vol = parseFloat(closeSheetVolume.value) || 0
  if (!vol) return 0
  const isLong = pos.direction === 'BUY'
  const refPrice = closeIsMarket.value
    ? (parseFloat(closeSheetMarkPriceStr.value) || parseFloat(pos.currentPrice) || 0)
    : (parseFloat(closeSheetPrice.value) || 0)
  if (!refPrice) return 0
  const open = parseFloat(pos.openPrice) || 0
  const diff = isLong ? (refPrice - open) : (open - refPrice)
  return diff * vol
})

async function confirmClose() {
  const pos = closeTargetPos.value
  if (!pos) return
  const vol = closeSheetVolume.value
  const volNum = parseFloat(vol)
  const avail = parseFloat(pos.availablePosition) || 0
  if (!vol || isNaN(volNum) || volNum <= 0) {
    closeSheetError.value = t('futuresPage.errors.enterQty')
    return
  }
  if (volNum > avail) {
    closeSheetError.value = t('futuresPage.errors.qtyExceedAvail')
    return
  }
  let ok = false
  if (closeIsMarket.value) {
    ok = await store.closePosition(pos.id, vol)
  } else {
    if (!closeSheetPrice.value) {
      closeSheetError.value = t('futuresPage.errors.enterPrice')
      return
    }
    ok = await store.closeLimitPosition(pos.id, closeSheetPrice.value, vol)
  }
  if (ok) {
    showCloseSheet.value = false
    closeTargetPos.value = null
  } else {
    closeSheetError.value = store.error || t('futuresPage.errors.closeFailed')
  }
}

// ── 二次确认（替代原生 confirm，与主题一致）────────────────
const showActionConfirm = ref(false)
const actionConfirmTitle = ref('')
const actionConfirmMessage = ref('')
const actionConfirmLoading = ref(false)
let pendingActionConfirm: (() => Promise<unknown>) | null = null

function openActionConfirm(title: string, message: string, action: () => Promise<unknown>) {
  actionConfirmTitle.value = title || t('futuresPage.modal.confirmDefaultTitle')
  actionConfirmMessage.value = message
  pendingActionConfirm = action
  showActionConfirm.value = true
}

function closeActionConfirm() {
  showActionConfirm.value = false
  pendingActionConfirm = null
  actionConfirmLoading.value = false
}

async function onActionConfirmOk() {
  const fn = pendingActionConfirm
  if (!fn) {
    closeActionConfirm()
    return
  }
  actionConfirmLoading.value = true
  try {
    await fn()
  } finally {
    closeActionConfirm()
  }
}

function onActionConfirmCancel() {
  if (actionConfirmLoading.value) return
  closeActionConfirm()
}

// ── Reverse position confirm ────────────────────────────
function reversePosition(pos: import('@/stores/futures').Position) {
  openActionConfirm(
    t('futuresPage.confirm.reverseTitle'),
    t('futuresPage.confirm.reverseBody', {
      pair: pos.coin?.symbol ?? '--',
      side: pos.direction === 'BUY' ? t('futuresPage.confirm.sideLongWord') : t('futuresPage.confirm.sideShortWord'),
    }),
    () => store.reversePosition(pos.id),
  )
}

// ── Cancel all orders confirm ───────────────────────────
function cancelAllOrders() {
  if (!currentOrders.value.length) return
  const n = currentOrders.value.length
  openActionConfirm(
    t('futuresPage.bottom.cancelAllOrders'),
    t('futuresPage.confirm.cancelAllBody', { n }),
    () => store.cancelAllOrders(),
  )
}

// ── Close all positions confirm ─────────────────────────
function confirmCloseAll() {
  if (!positions.value.length) return
  const n = positions.value.length
  openActionConfirm(
    t('futuresPage.bottom.closeAllPositions'),
    t('futuresPage.confirm.closeAllBody', { n }),
    () => store.closeAllPositions(),
  )
}

// ── Margin mode (全仓/分仓；对齐 flutter，后端只支持 positionType=0/1) ──
const MARGIN_MODES = computed<{ value: 'cross' | 'split'; label: string }[]>(() => [
  { value: 'cross', label: t('futuresPage.margin.cross') },
  { value: 'split', label: t('futuresPage.margin.split') },
])
const marginModeLabel = computed(() =>
  MARGIN_MODES.value.find(m => m.value === marginMode.value)?.label ?? t('futuresPage.margin.cross'))

async function selectMarginMode(mode: 'cross' | 'split') {
  showMarginDrop.value = false
  if (mode === marginMode.value) return
  await store.setMarginMode(mode)
}

// ── Amount unit (张/USDT/币) ─────────────────────────────
const AMOUNT_UNITS = computed<{ value: 'lots' | 'usdt' | 'base'; label: string }[]>(() => [
  { value: 'lots', label: t('futuresPage.amountUnit.lots') },
  { value: 'usdt', label: t('futuresPage.amountUnit.usdt') },
  { value: 'base', label: 'BASE' },
])
const amountUnitLabel = computed(() => {
  const u = amountUnit.value
  if (u === 'lots') {
    return t('futuresPage.amountUnit.lots')
  }
  if (u === 'usdt') {
    return t('futuresPage.amountUnit.usdt')
  }
  return baseCurrency.value
})
function selectAmountUnit(u: 'lots' | 'usdt' | 'base') {
  showAmountUnitDrop.value = false
  store.setAmountUnit(u)
  volume.value = ''
  pctSlider.value = 0
}

/** 与 flutter futures_screen._refPrice 一致：市价→最新价；限价/条件限价→委托价；条件市价→触发价 */
function orderRefPrice(): number {
  if (orderType.value === 'market') {
    return parseFloat(ticker.value?.close ?? '0') || 0
  }
  if (orderType.value === 'limit') {
    return parseFloat(entrustPrice.value) || 0
  }
  if (conditionalSubType.value === 'limit') {
    return parseFloat(entrustPrice.value) || 0
  }
  return parseFloat(triggerPrice.value) || 0
}

const futuresContractSize = computed(() => {
  const n = parseFloat(symbolInfo.value?.shareNumber ?? '1')
  return n > 0 ? n : 1
})

const futuresCoinPrecision = computed(() => {
  const s = symbolInfo.value?.minScale
  if (s == null || !Number.isFinite(Number(s))) return 4
  return Math.min(18, Math.max(0, Math.floor(Number(s))))
})

/** 与 flutter：usdtPrecision < 2 用 usdtPrecision，否则展示 2 位 */
const futuresUsdtDisplayDp = computed(() => {
  const u = symbolInfo.value?.baseCoinScale
  const prec = u == null || !Number.isFinite(Number(u)) ? 4 : Math.floor(Number(u))
  return prec < 2 ? prec : 2
})

const futuresOpenFeeRate = computed(() => {
  const f = parseFloat(symbolInfo.value?.openFee ?? '0.0005')
  return f > 0 ? f : 0.0005
})

/** 最大可开（当前单位），与 flutter _calcMaxOpenAmount 一致 */
const maxOpenAmountDisplay = computed(() => {
  const info = symbolInfo.value
  const refP = orderRefPrice()
  const avail = parseFloat(swapAvailBalance.value) || 0
  const lev = leverage.value || 1
  const cs = futuresContractSize.value
  const feeRate = futuresOpenFeeRate.value
  if (!info || avail <= 0 || refP <= 0) return '--'
  const feeMult = 1 + feeRate * lev
  const maxNotional = (avail / feeMult) * lev
  const u = amountUnit.value
  const coinPrec = futuresCoinPrecision.value
  if (u === 'usdt') {
    const f = 100
    const t = Math.trunc(maxNotional * f) / f
    return t.toFixed(2)
  }
  if (u === 'lots') {
    return String(Math.floor(maxNotional / cs + 1e-12))
  }
  const f = 10 ** coinPrec
  const t = Math.trunc((maxNotional / refP) * f) / f
  return t.toFixed(coinPrec)
})

function floorToDecimals(val: number, dp: number): number {
  const f = 10 ** dp
  return Math.floor(val * f + 1e-12) / f
}

function truncateToCoinPrecision(val: number): number {
  const dp = futuresCoinPrecision.value
  const f = 10 ** dp
  return Math.trunc(val * f) / f
}

/** 换算为下单用的基础币数量（与 flutter placeOrder 中 volumeInBtc + floor 一致） */
function toBaseVolume(input: string): string {
  const v = parseFloat(input)
  if (isNaN(v) || v <= 0) return ''
  const price = orderRefPrice()
  if (price <= 0) return ''
  const cs = futuresContractSize.value
  const u = amountUnit.value
  let volumeInBase = 0
  if (u === 'base') volumeInBase = v
  else if (u === 'usdt') volumeInBase = v / price
  else volumeInBase = (v * cs) / price
  const finalVol = truncateToCoinPrecision(volumeInBase)
  if (finalVol <= 0) return ''
  const dp = futuresCoinPrecision.value
  return finalVol.toFixed(dp)
}

function setPctSlider(p: number) {
  error.value = ''
  if (p > 0 && orderRefPrice() <= 0) {
    if (orderType.value === 'conditional' && conditionalSubType.value === 'market') {
      error.value = t('futuresPage.errors.enterTriggerPrice')
    } else if (orderType.value !== 'market') {
      error.value = t('futuresPage.errors.enterPrice')
    } else {
      error.value = t('futuresPage.errors.priceNotReadyRetry')
    }
    return
  }
  pctSlider.value = p
}

// ── Leverage panel (步进 + 滑块 + min/max) ────────────────
const leverageOptions = computed(() => {
  const raw = symbolInfo.value?.leverage
  if (!raw) return [1, 5, 10, 20, 50, 100]
  return raw.split(',').map(s => parseInt(s, 10)).filter(n => !isNaN(n) && n > 0)
})
const leverageMin = computed(() => leverageOptions.value[0] ?? 1)
const leverageMax = computed(() => leverageOptions.value[leverageOptions.value.length - 1] ?? 125)
/** leverageType=1：固定档位（仅 pill 选择）；leverageType=2：区间任意值（步进+滑块） */
const isDiscreteLeverage = computed(() => symbolInfo.value?.leverageType === 1)
const leverageDraft = ref(leverage.value)
watch(showLeveragePanel, (v) => { if (v) leverageDraft.value = leverage.value })
watch(leverage, (v) => { leverageDraft.value = v })
function leverageStep(delta: number) {
  const next = Math.max(leverageMin.value, Math.min(leverageMax.value, leverageDraft.value + delta))
  leverageDraft.value = next
}
async function confirmLeverage() {
  await store.modifyLeverage(leverageDraft.value)
  showLeveragePanel.value = false
}

// ── Helpers ────────────────────────────────────────────
function pct(v: string) {
  const n = parseFloat(v)
  return isNaN(n) ? '--' : (n >= 0 ? '+' : '') + (n * 100).toFixed(2) + '%'
}
// 截断到小数点后2位，不四舍五入
function trunc2(v: string | number): string {
  const s = Number(v).toFixed(10)
  const dot = s.indexOf('.')
  return dot === -1 ? s + '.00' : s.slice(0, dot + 3)
}

/** 合约 USDT 价格展示小数位：与后端 symbolInfo.coinScale（无则 minScale）一致，默认 2 */
function futuresPriceDecimals(): number {
  const info = symbolInfo.value
  const raw = info?.coinScale ?? info?.minScale
  if (raw == null || !Number.isFinite(Number(raw))) return 2
  return Math.min(18, Math.max(0, Math.floor(Number(raw))))
}

function fmtPrice(v: string | number | null | undefined) {
  if (v == null) return '--'
  const n = typeof v === 'string' ? parseFloat(v) : v
  if (isNaN(n)) return '--'
  const dp = futuresPriceDecimals()
  return n.toLocaleString('en-US', { minimumFractionDigits: dp, maximumFractionDigits: dp })
}
function fmtVol(v: string) {
  const n = parseFloat(v)
  if (isNaN(n)) return '--'
  if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B'
  if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M'
  if (n >= 1e3) return (n / 1e3).toFixed(2) + 'K'
  return n.toFixed(2)
}
function fmtTime(ts: number | string | null | undefined) {
  if (!ts) return '--'
  let ms = typeof ts === 'string' ? Number(ts) : ts
  if (isNaN(ms) || ms <= 0) return '--'
  // 秒级时间戳（< 1e12）转毫秒
  if (ms < 1_000_000_000_000) ms *= 1000
  const d = new Date(ms)
  if (isNaN(d.getTime())) return '--'
  const M = String(d.getMonth() + 1).padStart(2, '0')
  const D = String(d.getDate()).padStart(2, '0')
  const h = String(d.getHours()).padStart(2, '0')
  const m = String(d.getMinutes()).padStart(2, '0')
  return `${M}/${D} ${h}:${m}`
}
function fmtTimeShort(ts: number) {
  const d = new Date(ts)
  return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`
}
function fmtDateTime(ts: number) {
  const d = new Date(ts)
  const Y = d.getFullYear(), M = String(d.getMonth()+1).padStart(2,'0'), D = String(d.getDate()).padStart(2,'0')
  const h = String(d.getHours()).padStart(2,'0'), m = String(d.getMinutes()).padStart(2,'0'), s = String(d.getSeconds()).padStart(2,'0')
  return `${Y}/${M}/${D} ${h}:${m}:${s}`
}
function profitClass(v: string | number) { return parseFloat(String(v)) >= 0 ? 'up' : 'dn' }

// 用实时 ticker 价格计算浮动盈亏
function calcLivePnl(pos: import('@/stores/futures').Position): number {
  // 优先使用API返回的实时盈亏，回退到本地计算
  if (pos.currentProfit && parseFloat(pos.currentProfit) !== 0) return parseFloat(pos.currentProfit)
  const livePrice = ticker.value?.close ? parseFloat(ticker.value.close) : parseFloat(pos.currentPrice)
  const openPrice = parseFloat(pos.openPrice)
  const vol = parseFloat(pos.currentPosition)
  if (isNaN(livePrice) || isNaN(openPrice) || isNaN(vol)) return 0
  return pos.direction === 'BUY'
    ? (livePrice - openPrice) * vol
    : (openPrice - livePrice) * vol
}
function calcLivePnlPct(pos: import('@/stores/futures').Position): number {
  // ROE = unrealizedPnl / currentMargin * 100（与flutter一致）
  const margin = parseFloat(pos.currentMargin)
  if (margin > 0) return calcLivePnl(pos) / margin * 100
  // fallback：用开仓价×持仓量/杠杆估算保证金
  const openPrice = parseFloat(pos.openPrice)
  const vol = parseFloat(pos.currentPosition)
  if (!openPrice || !vol || !pos.leverage) return 0
  const fallbackMargin = openPrice * vol / pos.leverage
  return fallbackMargin > 0 ? calcLivePnl(pos) / fallbackMargin * 100 : 0
}
function dirLabel(d: string) {
  return d === 'BUY' ? t('futuresPage.directions.long') : t('futuresPage.directions.short')
}
// 开多/开空/平多/平空（与flutter _directionInfo一致）
function orderDirLabel(direction: string, entrustType: string) {
  const isBuy = direction === 'BUY' || direction === '0'
  const isClose = entrustType === 'CLOSE' || entrustType === '1'
  if (!isClose) {
    return isBuy ? t('futuresPage.directions.openLong') : t('futuresPage.directions.openShort')
  }
  return isBuy ? t('futuresPage.directions.closeShort') : t('futuresPage.directions.closeLong')
}
function orderDirClass(direction: string, entrustType: string) {
  const isBuy = direction === 'BUY' || direction === '0'
  const isClose = entrustType === 'CLOSE' || entrustType === '1'
  // 开多(绿)/平空(绿)/开空(红)/平多(红)
  return (!isClose && isBuy) || (isClose && isBuy) ? 'up' : 'dn'
}
function typeLabel(tRaw: number | string | null | undefined) {
  const s = String(tRaw ?? '')
  if (s === '1' || s === 'LIMIT_PRICE' || s === 'LIMIT') {
    return t('futuresPage.orderTypes.limit')
  }
  if (s === '2' || s === 'SPOT_LIMIT' || s === 'PLAN' || s === 'STOP') {
    return t('futuresPage.orderTypes.plan')
  }
  if (s === '3') {
    return t('futuresPage.orderTypes.takeProfit')
  }
  if (s === '4') {
    return t('futuresPage.orderTypes.stopLoss')
  }
  return t('futuresPage.orderTypes.marketShort')
}
// 委托价格显示：市价/计划市价→"市价"，限价/计划限价→显示原始价格
function orderPriceDisplay(entrustPrice: string, openType: number, _triggerPrice?: string) {
  if (openType === 0) {
    return t('futuresPage.orderTypes.marketShort')
  }
  if (openType === 2) {
    // 计划委托：entrustPrice=0 时为计划市价
    const ep = parseFloat(entrustPrice)
    if (!ep || ep === 0) {
      return t('futuresPage.orderTypes.marketShort')
    }
  }
  return fmtPrice(entrustPrice)
}
function histDirLabel(d: string, entrustType: string) {
  return orderDirLabel(d, entrustType)
}
function histDirClass(d: string, entrustType: string) {
  return orderDirClass(d, entrustType)
}
// 历史状态标签
function statusLabel(status: string | number) {
  const s = String(status ?? '').toUpperCase()
  if (s === 'ENTRUST_SUCCESS' || s === 'FILLED' || s === 'DONE' || s === 'PARTLYDONE') {
    return t('futuresPage.orderStatus.filled')
  }
  if (s === 'ENTRUST_ING' || s === 'OPEN' || s === 'STARTED') {
    return t('futuresPage.orderStatus.open')
  }
  if (s === 'ENTRUST_CANCEL' || s === 'CANCELLED') {
    return t('futuresPage.orderStatus.cancelled')
  }
  if (s === 'ENTRUST_FAILURE' || s === 'FAILED') {
    return t('futuresPage.orderStatus.failed')
  }
  return status ? String(status) : t('futuresPage.orderStatus.filled')
}
function statusClass(status: string | number) {
  const s = String(status ?? '').toUpperCase()
  if (s === 'ENTRUST_CANCEL' || s === 'CANCELLED' || s === 'ENTRUST_FAILURE' || s === 'FAILED') return 'dn'
  return 'up'
}
// 历史仓位平仓类型
function closeTypeLabel(closeType: number | string) {
  const ty = Number(closeType)
  if (ty === 1) {
    return t('futuresPage.closeTypes.market')
  }
  if (ty === 2) {
    return t('futuresPage.closeTypes.oneClick')
  }
  if (ty === 3) {
    return t('futuresPage.closeTypes.reverseOpen')
  }
  if (ty === 4) {
    return t('futuresPage.closeTypes.tpSl')
  }
  if (ty === 5) {
    return t('futuresPage.closeTypes.liquidation')
  }
  if (ty === 6) {
    return t('futuresPage.closeTypes.admin')
  }
  return ''
}
// 历史委托价格显示（区分开仓/平仓单）
function histOrdOpenPrice(ord: import('@/stores/futures').HistoryOrder) {
  const isClose = ord.entrustType === 'CLOSE' || String(ord.entrustType) === '1'
  return isClose ? fmtPrice(ord.usdtOpenPrice) : fmtPrice(ord.tradedPrice)
}
function histOrdEntrustPrice(ord: import('@/stores/futures').HistoryOrder) {
  const s = String(ord.type ?? '')
  const isMarket = s === '0' || s === 'MARKET_PRICE' || s === 'MARKET'
  const isPlan = s === '2' || s === 'SPOT_LIMIT' || s === 'PLAN'
  if (isMarket) {
    return t('futuresPage.orderTypes.marketShort')
  }
  const ep = parseFloat(ord.entrustPrice || '0')
  if (isPlan && (!ep || ep === 0)) {
    return t('futuresPage.orderTypes.marketShort')
  }
  return ep > 0 ? fmtPrice(ord.entrustPrice) : '--'
}
function barWidth(items: any[], total: string) {
  const maxTotal = Math.max(...items.map((i: any) => parseFloat(i.total) || 0))
  if (!maxTotal) return '0%'
  return Math.min((parseFloat(total) / maxTotal) * 100, 100) + '%'
}
const crosshairMa = computed(() => {
  const kd = crosshairData.value?.kLineData
  if (!kd || !currentBars.length) return null
  const idx = currentBars.findIndex((b: any) => b.timestamp === kd.timestamp)
  if (idx < 0) return null
  const calcMa = (period: number): string | null => {
    if (idx < period - 1) return null
    const sum = currentBars.slice(idx - period + 1, idx + 1).reduce((s: number, b: any) => s + b.close, 0)
    return (sum / period).toFixed(futuresPriceDecimals())
  }
  return { ma5: calcMa(5), ma10: calcMa(10), ma30: calcMa(30) }
})

const bsRatio = computed(() => {
  const bidTotal = orderBook.value.bid.items.reduce((s, i) => s + parseFloat(i.amount || '0'), 0)
  const askTotal = orderBook.value.ask.items.reduce((s, i) => s + parseFloat(i.amount || '0'), 0)
  const total = bidTotal + askTotal
  if (!total) return { buy: 50, sell: 50 }
  return { buy: Math.round((bidTotal / total) * 100), sell: Math.round((askTotal / total) * 100) }
})

function recalcBookRowCount() {
  const col = bookColRef.value
  if (!col) return
  const rowEl = col.querySelector('.book-row') as HTMLElement | null
  const rowH = Math.max(1, rowEl?.offsetHeight || 22)
  const tabsH = bookTabsRef.value?.offsetHeight || 40
  const ctrlH = bookCtrlRef.value?.offsetHeight || 34
  const headH = bookHeadRef.value?.offsetHeight || 24
  const ratioH = bsRatioRef.value?.offsetHeight || 28
  const midH = bookLayout.value === 'full' ? (bookMidRef.value?.offsetHeight || 32) : 0
  const usable = col.clientHeight - tabsH - ctrlH - headH - ratioH - midH
  if (usable <= 0) return
  const rows = Math.floor((usable + rowH * 0.5) / rowH)
  dynamicBookRows.value = Math.min(60, Math.max(4, rows))
}

const bookRowCount = computed(() => {
  const rows = dynamicBookRows.value
  return bookLayout.value === 'full' ? Math.max(1, Math.ceil(rows / 2)) : rows
})

function addDepthTotals(rows: [number, number][]) {
  let acc = 0
  return rows.map(([price, qty]) => {
    acc += qty
    return { price: String(price), amount: String(qty), total: String(acc) }
  })
}

const displayAskItems = computed(() => {
  const n = bookRowCount.value
  const take = bookLayout.value === 'bids' ? 0 : n
  if (take <= 0) return []
  // 卖盘：从最小价开始取（最接近成交价），展示按价格从大到小
  const sliceAsc = rawBookAsks.value.slice(0, Math.min(take, rawBookAsks.value.length))
  const display = [...sliceAsc].sort((a, b) => b[0] - a[0])
  return addDepthTotals(display)
})

const displayBidItems = computed(() => {
  const n = bookRowCount.value
  const take = bookLayout.value === 'asks' ? 0 : n
  if (take <= 0) return []
  const display = rawBookBids.value.slice(0, Math.min(take, rawBookBids.value.length))
  return addDepthTotals(display)
})

const bookMidPrice = computed(() => {
  if (klineRealtimeClose.value !== null && Number.isFinite(klineRealtimeClose.value)) {
    return fmtPrice(klineRealtimeClose.value)
  }
  return ticker.value ? fmtPrice(ticker.value.close) : '--'
})

// ── Lifecycle ──────────────────────────────────────────
onMounted(async () => {
  const sym = String(route.params.symbol)
  await store.init(sym)
  if (isLoggedIn.value) {
    store.fetchPositions()
    store.fetchCurrentOrders()
    store.fetchLeverage()
    startPolling()
  }
  await nextTick()
  initChart(); updateChart()
  recalcBookRowCount()
  if (typeof ResizeObserver !== 'undefined' && bookColRef.value) {
    bookRo = new ResizeObserver(() => recalcBookRowCount())
    bookRo.observe(bookColRef.value)
  }
  if (!syncEntrustPriceFromCoinList() && ticker.value && !entrustPrice.value) {
    entrustPrice.value = ticker.value.close
  }
  ws.connect(sym, klineResolution.value)
  countdownTimer = setInterval(updateCountdown, 1000)
  window.addEventListener('resize', onWindowResize)
})

watch(() => route.params.symbol, async (sym) => {
  if (!sym) return
  // 切换币对：重置下单输入，避免沿用上一币对的数量与百分比
  entrustPrice.value = ''
  triggerPrice.value = ''
  prevTickClose = 0
  volume.value = ''
  pctSlider.value = 0
  recentTrades.value = []
  rawBookAsks.value = []
  rawBookBids.value = []
  klineRealtimeClose.value = null
  destroyChart()
  await store.init(String(sym))
  if (isLoggedIn.value) {
    store.fetchPositions()
    store.fetchCurrentOrders()
    store.fetchLeverage()
  }
  await nextTick()
  initChart(); updateChart()
  recalcBookRowCount()
  syncEntrustPriceFromCoinList()
  ws.changeSymbol(String(sym))
})

watch([bookLayout, bookTab], async () => {
  await nextTick()
  recalcBookRowCount()
})

watch(isLoggedIn, (loggedIn) => {
  if (loggedIn) {
    store.fetchPositions()
    store.fetchCurrentOrders()
    store.fetchLeverage()
    startPolling()
  } else {
    stopPolling()
  }
})

watch(ticker, (t) => {
  if (t && !entrustPrice.value) entrustPrice.value = t.close
  if (t) document.title = `${symbol.value} ${fmtPrice(t.close)} | iBit`
})

function applyPctToVolume(pct: number) {
  const refP = orderRefPrice()
  const avail = parseFloat(swapAvailBalance.value) || 0
  const lev = leverage.value || 1
  const cs = futuresContractSize.value
  const feeRate = futuresOpenFeeRate.value
  if (pct === 0 || avail <= 0) {
    volume.value = ''
    return
  }
  if (refP <= 0) {
    volume.value = ''
    return
  }
  // 与 flutter _PercentSlider 开仓分支：feeMultiplier、effectiveMargin、maxNotional
  const feeMult = 1 + feeRate * lev
  const effectiveMargin = (avail / feeMult) * (pct / 100)
  const maxNotional = effectiveMargin * lev
  const u = amountUnit.value
  if (u === 'usdt') {
    const dp = futuresUsdtDisplayDp.value
    volume.value = floorToDecimals(maxNotional, dp).toFixed(dp)
  } else if (u === 'base') {
    const rawBtc = maxNotional / refP
    volume.value = truncateToCoinPrecision(rawBtc).toFixed(futuresCoinPrecision.value)
  } else {
    const maxL = Math.floor((effectiveMargin * lev) / cs + 1e-12)
    volume.value = maxL > 0 ? String(maxL) : ''
  }
}

watch(pctSlider, (pct) => {
  applyPctToVolume(pct)
})

// 币对、价格、杠杆、手续费相关变化时，百分比数量按当前参照价重算（对齐 flutter _refreshMaxAmount）
watch([
  amountUnit, swapAvailBalance, leverage, ticker, entrustPrice, triggerPrice,
  orderType, conditionalSubType, symbolInfo,
], () => {
  if (pctSlider.value > 0) applyPctToVolume(pctSlider.value)
})

// 与 flutter：切换下单类型时清空数量与百分比
watch(orderType, () => {
  volume.value = ''
  pctSlider.value = 0
})
watch(conditionalSubType, () => {
  volume.value = ''
  pctSlider.value = 0
})

// 切到子导航 tab 时加载数据 / 恢复图表
watch(subNavTab, (tab) => {
  // 图表 canvas 始终保持 position:absolute 渲染，尺寸不会归零
  // 切回图表时只需 resize 更新布局即可
  if (tab === 'chart') {
    nextTick(() => { chart?.resize() })
  }
  if (tab === 'coin-info' && !coinExtInfo.value) {
    store.fetchCoinExtInfo()
  }
  if (tab === 'funding-rate') {
    store.fetchFundingRateCurrent()
    store.fetchFundingRateHistory(true)
  }
})

onUnmounted(() => {
  destroyChart()
  ws.disconnect()
  stopPolling()
  if (countdownTimer !== null) clearInterval(countdownTimer)
  window.removeEventListener('resize', onWindowResize)
  frListRo?.disconnect()
  bookRo?.disconnect()
  bookRo = null
})
</script>

<template>
  <div class="ft-page" @click="showPairSelector = false; showLeveragePanel = false; showMarginDrop = false; showPrecDrop = false; showCondDrop = false; showAmountUnitDrop = false">

    <!-- ═══ TICKER BAR ═══ -->
    <div class="ticker-bar">
      <div class="pair-trigger" @click.stop="showPairSelector = !showPairSelector">
        <div class="coin-icon">
          <img v-if="currentCoinIcon" :src="currentCoinIcon" :alt="symbol" />
          <span v-else class="coin-icon-fb">{{ symbol.split('/')[0]?.[0] }}</span>
        </div>
        <div class="pair-name-col">
          <div class="pair-name-row">
            <span class="pair-name">{{ symbol }}</span>
            <svg class="caret" :class="{ open: showPairSelector }" width="10" height="6" viewBox="0 0 10 6">
              <path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/>
            </svg>
          </div>
          <span class="pair-sub">{{ t('futuresPage.perpetual') }}</span>
        </div>
        <Transition name="pop">
          <div v-if="showPairSelector" class="pair-drop" @click.stop>
            <div class="pair-drop-search">
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#6a7182" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
              <input v-model="pairSearch" class="pair-search-input" :placeholder="t('futuresPage.pairSearchPlaceholder')" autofocus />
            </div>
			
            <div class="pair-list">
              <div
                v-for="t in filteredTickers" :key="t.symbol"
                class="pair-item" :class="{ active: t.symbol === symbol }"
                @click="selectPair(t.symbol)"
              >
                <div class="pi-icon">
                  <img v-if="coinIcon(t.symbol)" :src="coinIcon(t.symbol)" :alt="t.symbol" />
                  <span v-else class="pi-icon-fb">{{ t.symbol[0] }}</span>
                </div>
                <span class="pi-sym">{{ t.symbol }}</span>
                <span class="pi-price">{{ fmtPrice(t.close) }}</span>
                <span class="pi-chg" :class="parseFloat(t.chg) >= 0 ? 'up' : 'dn'">{{ pct(t.chg) }}</span>
              </div>
              <div v-if="!filteredTickers.length" class="pair-empty">{{ t('futuresPage.pairEmpty') }}</div>
            </div>
          </div>
        </Transition>
      </div>

      <div class="ticker-price-wrap">
        <div class="ticker-price" :class="priceUp ? 'up' : 'dn'">{{ ticker ? fmtPrice(ticker.close) : '--' }}</div>
        <div class="ticker-usd">≈ ${{ ticker ? fmtPrice(ticker.close) : '--' }}</div>
      </div>

      <div class="ticker-stats">
        <div class="ts-item">
          <span class="ts-lbl">{{ t('futuresPage.stats.change24h') }}</span>
          <span class="ts-val" :class="priceUp ? 'up' : 'dn'">{{ ticker ? pct(ticker.chg) : '--' }}</span>
        </div>
        <div class="ts-item">
          <span class="ts-lbl">{{ t('futuresPage.stats.markPrice') }}</span>
          <span class="ts-val">{{ wsMarkPrice ? fmtPrice(wsMarkPrice) : (ticker ? fmtPrice(ticker.close) : '--') }}</span>
        </div>
        <div class="ts-item">
          <span class="ts-lbl">{{ t('futuresPage.stats.high24h') }}</span>
          <span class="ts-val">{{ ticker ? fmtPrice(ticker.high) : '--' }}</span>
        </div>
        <div class="ts-item">
          <span class="ts-lbl">{{ t('futuresPage.stats.low24h') }}</span>
          <span class="ts-val">{{ ticker ? fmtPrice(ticker.low) : '--' }}</span>
        </div>
        <div class="ts-item">
          <span class="ts-lbl">{{ t('futuresPage.stats.volume24h') }}</span>
          <span class="ts-val">{{ ticker ? fmtVol(ticker.turnover) : '--' }} USDT</span>
        </div>
        <div class="ts-item">
          <span class="ts-lbl">{{ t('futuresPage.stats.fundingCountdown') }}</span>
          <span class="ts-val">
            <span :class="wsFundingRate && parseFloat(wsFundingRate) < 0 ? 'dn' : 'up'">
              {{ wsFundingRate ? (parseFloat(wsFundingRate) >= 0 ? '+' : '') + (parseFloat(wsFundingRate) * 100).toFixed(4) + '%' : '--' }}
            </span>
            / {{ fundingCountdown }}
          </span>
        </div>
      </div>
    </div>

    <!-- ═══ MOBILE TAB BAR ═══ -->
    <div v-if="isMobile" class="mobile-trade-tabs">
      <button class="mtt-btn" :class="{ act: mobileTab === 'chart' }" @click="mobileTab = 'chart'">{{ t('futuresPage.mobileTabs.chart') }}</button>
      <button class="mtt-btn" :class="{ act: mobileTab === 'trade' }" @click="mobileTab = 'trade'">{{ t('futuresPage.mobileTabs.trade') }}</button>
    </div>

    <!-- ═══ PAGE BODY (CSS Grid) ═══ -->
    <div class="page-body">

      <!-- LEFT + MIDDLE: chart-area -->
      <div class="chart-area" :class="{ 'mobile-hidden': isMobile && mobileTab !== 'chart' }">
        <!-- Loading -->
        <div v-if="loading" class="chart-loading">
          <div class="spinner"></div>
        </div>

        <div v-if="!loading" class="chart-plus-book">
          <!-- COL 1: Chart -->
          <div class="chart-col">
            <!-- Drawing sidebar -->
            <div class="draw-side">
              <button class="db" :class="{ 'db--act': activeDrawTool === 'cursor' }" :title="t('futuresPage.draw.cursor')" @click="activateDrawTool('cursor')">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M5 3l14 9-7 1-3 7z"/></svg>
              </button>
              <div class="db-sep"/>
              <button class="db" :class="{ 'db--act': activeDrawTool === 'trend' }" :title="t('futuresPage.draw.trend')" @click="activateDrawTool('trend')">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><line x1="4" y1="20" x2="20" y2="4"/><circle cx="4" cy="20" r="1.8" fill="currentColor"/><circle cx="20" cy="4" r="1.8" fill="currentColor"/></svg>
              </button>
              <button class="db" :class="{ 'db--act': activeDrawTool === 'hline' }" :title="t('futuresPage.draw.hline')" @click="activateDrawTool('hline')">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><line x1="3" y1="12" x2="21" y2="12"/><line x1="5" y1="9" x2="5" y2="15"/><line x1="19" y1="9" x2="19" y2="15"/></svg>
              </button>
              <button class="db" :class="{ 'db--act': activeDrawTool === 'ray' }" :title="t('futuresPage.draw.ray')" @click="activateDrawTool('ray')">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><line x1="4" y1="20" x2="20" y2="4"/><circle cx="4" cy="20" r="1.8" fill="currentColor"/></svg>
              </button>
              <button class="db" :class="{ 'db--act': activeDrawTool === 'channel' }" :title="t('futuresPage.draw.channel')" @click="activateDrawTool('channel')">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><line x1="3" y1="17" x2="21" y2="7"/><line x1="3" y1="21" x2="21" y2="11"/></svg>
              </button>
              <button class="db" :class="{ 'db--act': activeDrawTool === 'fib' }" :title="t('futuresPage.draw.fib')" @click="activateDrawTool('fib')">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                  <line x1="3" y1="19" x2="21" y2="19"/><line x1="3" y1="14" x2="21" y2="14"/>
                  <line x1="3" y1="10" x2="21" y2="10"/><line x1="3" y1="6" x2="21" y2="6"/>
                </svg>
              </button>
              <div class="db-sep"/>
              <button class="db" :class="{ 'db--act': activeDrawTool === 'text' }" :title="t('futuresPage.draw.text')" @click="activateDrawTool('text')">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><text x="4" y="18" font-size="14" font-weight="700" font-family="Microsoft YaHei, sans-serif">T</text></svg>
              </button>
              <div class="db-sep"/>
              <button class="db" :title="t('futuresPage.draw.clearAll')" @click="clearOverlays()">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4h6v2"/></svg>
              </button>
              <div class="db-spacer"/>
              <button class="db" :title="t('futuresPage.draw.zoomOut')" @click="zoomOut()">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="10" cy="10" r="6"/><line x1="14.5" y1="14.5" x2="20" y2="20"/><line x1="7" y1="10" x2="13" y2="10"/></svg>
              </button>
            </div>

            <!-- Chart main -->
            <div class="chart-main">
              <!-- Sub-nav tabs -->
              <div class="sub-nav">
                <button class="snav-tab" :class="{ act: subNavTab === 'chart' }" @click="subNavTab = 'chart'">{{ t('futuresPage.chartSub.chart') }}</button>
                <button class="snav-tab" :class="{ act: subNavTab === 'coin-info' }" @click="subNavTab = 'coin-info'">{{ t('futuresPage.chartSub.coinInfo') }}</button>
                <button class="snav-tab" :class="{ act: subNavTab === 'funding-rate' }" @click="subNavTab = 'funding-rate'">{{ t('futuresPage.chartSub.fundingHistory') }}</button>
              </div>
              <!-- TF bar -->
              <div v-show="subNavTab === 'chart'" class="tf-bar">
                <button v-for="opt in TF_OPTIONS" :key="opt.value"
                  class="tf-btn" :class="{ act: klineResolution === opt.value }"
                  @click="klineResolution = opt.value; ws.changeResolution(opt.value)">{{ opt.label }}</button>
                <div class="tf-spacer"/>
                <template v-if="showMA && klines.length">
                  <span class="ma-leg ma5">MA5</span>
                  <span class="ma-leg ma10">MA10</span>
                  <span class="ma-leg ma30">MA30</span>
                </template>
                <button class="tf-ind" :class="{ act: showMA }" @click="showMA = !showMA">MA</button>
                <button class="tf-ind" :class="{ act: showVol }" @click="showVol = !showVol">VOL</button>
                <div class="tf-div"/>
                <button class="tf-icon" :title="t('futuresPage.chartTools.fullscreen')">
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M21 16v3a2 2 0 0 1-2 2h-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>
                </button>
                <button class="tf-icon" :title="t('futuresPage.chartTools.alert')">
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 0 1-3.46 0"/></svg>
                </button>
              </div>

              <!-- Canvas + overlays -->
              <div class="chart-area-rel">
              <div class="chart-canvas-wrap" @mouseleave="crosshairData = null">
                <!-- Top strip: symbol/period -->
                <div class="ohlcv-overlay">
                  <span class="ohlcv-sym">{{ displaySymbol }} · {{ TF_OPTIONS.find(t => t.value === klineResolution)?.label }} · iBit</span>
                  <template v-if="!crosshairData?.kLineData && ticker">
                    <span class="ohlcv-item">{{ t('futuresPage.ohlcv.open') }}=<em>{{ fmtPrice(ticker.open) }}</em></span>
                    <span class="ohlcv-item">{{ t('futuresPage.ohlcv.high') }}=<em class="up">{{ fmtPrice(ticker.high) }}</em></span>
                    <span class="ohlcv-item">{{ t('futuresPage.ohlcv.low') }}=<em class="dn">{{ fmtPrice(ticker.low) }}</em></span>
                    <span class="ohlcv-item">{{ t('futuresPage.ohlcv.close') }}=<em>{{ fmtPrice(ticker.close) }}</em></span>
                  </template>
                </div>
                <!-- Hover detail panel -->
                <div class="kline-tooltip" v-if="crosshairData?.kLineData" :style="tooltipStyle">
                  <div class="kt-time">{{ fmtDateTime(crosshairData.kLineData.timestamp) }}</div>
                  <div class="kt-row">
                    <span class="kt-lbl">{{ t('futuresPage.ohlcv.open') }}</span>
                    <span class="kt-val" :class="crosshairData.kLineData.close >= crosshairData.kLineData.open ? 'up' : 'dn'">{{ fmtPrice(crosshairData.kLineData.open) }}</span>
                  </div>
                  <div class="kt-row">
                    <span class="kt-lbl">{{ t('futuresPage.ohlcv.high') }}</span>
                    <span class="kt-val up">{{ fmtPrice(crosshairData.kLineData.high) }}</span>
                  </div>
                  <div class="kt-row">
                    <span class="kt-lbl">{{ t('futuresPage.ohlcv.low') }}</span>
                    <span class="kt-val dn">{{ fmtPrice(crosshairData.kLineData.low) }}</span>
                  </div>
                  <div class="kt-row">
                    <span class="kt-lbl">{{ t('futuresPage.ohlcv.close') }}</span>
                    <span class="kt-val" :class="crosshairData.kLineData.close >= crosshairData.kLineData.open ? 'up' : 'dn'">{{ fmtPrice(crosshairData.kLineData.close) }}</span>
                  </div>
                  <div class="kt-row">
                    <span class="kt-lbl">{{ t('futuresPage.ohlcv.volume') }}</span>
                    <span class="kt-val">{{ crosshairData.kLineData.volume?.toFixed(4) }}</span>
                  </div>
                  <div class="kt-row">
                    <span class="kt-lbl">{{ t('futuresPage.ohlcv.changePct') }}</span>
                    <span class="kt-val" :class="crosshairData.kLineData.close >= crosshairData.kLineData.open ? 'up' : 'dn'">
                      {{ ((crosshairData.kLineData.close - crosshairData.kLineData.open) / crosshairData.kLineData.open * 100).toFixed(2) }}%
                    </span>
                  </div>
                  <div class="kt-row">
                    <span class="kt-lbl">{{ t('futuresPage.ohlcv.amplitude') }}</span>
                    <span class="kt-val">{{ ((crosshairData.kLineData.high - crosshairData.kLineData.low) / crosshairData.kLineData.open * 100).toFixed(2) }}%</span>
                  </div>
                  <template v-if="crosshairMa">
                    <div class="kt-row" v-if="crosshairMa.ma5 !== null">
                      <span class="kt-lbl kt-c-ma5">MA5</span>
                      <span class="kt-val kt-c-ma5">{{ crosshairMa.ma5 }}</span>
                    </div>
                    <div class="kt-row" v-if="crosshairMa.ma10 !== null">
                      <span class="kt-lbl kt-c-ma10">MA10</span>
                      <span class="kt-val kt-c-ma10">{{ crosshairMa.ma10 }}</span>
                    </div>
                    <div class="kt-row" v-if="crosshairMa.ma30 !== null">
                      <span class="kt-lbl kt-c-ma30">MA30</span>
                      <span class="kt-val kt-c-ma30">{{ crosshairMa.ma30 }}</span>
                    </div>
                  </template>
                </div>
                <div ref="chartEl" class="kline-canvas"/>
              </div><!-- /chart-canvas-wrap -->

              <!-- 币种资料面板 -->
              <div v-show="subNavTab === 'coin-info'" class="coin-info-panel">
                <div v-if="coinExtLoading" class="ci-loading"><span class="spin-sm"/></div>
                <template v-else-if="coinExtInfo">
                  <div class="cip-header">
                    <img v-if="coinExtInfo.icon" :src="coinExtInfo.icon" class="cip-icon" alt="icon"/>
                    <div class="cip-names">
                      <span class="cip-name-cn">{{ coinExtInfo.nameCn || coinExtInfo.name || symbol.split('/')[0] }}</span>
                      <span class="cip-name-en">{{ coinExtInfo.nameEn || symbol.split('/')[0] }}</span>
                    </div>
                    <span v-if="coinExtInfo.rank" class="cip-rank">#{{ coinExtInfo.rank }}</span>
                  </div>
                  <div class="cip-grid">
                    <div class="cip-item">
                      <span class="cip-lbl">{{ t('futuresPage.coinInfo.marketCap') }}</span>
                      <span class="cip-val">{{ coinExtInfo.marketCap || '--' }}</span>
                    </div>
                    <div class="cip-item">
                      <span class="cip-lbl">{{ t('futuresPage.coinInfo.circulating') }}</span>
                      <span class="cip-val">{{ coinExtInfo.circulatingSupply || '--' }}</span>
                    </div>
                    <div class="cip-item">
                      <span class="cip-lbl">{{ t('futuresPage.coinInfo.issuePrice') }}</span>
                      <span class="cip-val">{{ coinExtInfo.issuePrice != null ? '$' + Number(coinExtInfo.issuePrice).toFixed(6) : '--' }}</span>
                    </div>
                    <div class="cip-item">
                      <span class="cip-lbl">{{ t('futuresPage.coinInfo.ath') }}</span>
                      <span class="cip-val up">{{ coinExtInfo.athPrice != null ? '$' + Number(coinExtInfo.athPrice).toFixed(2) : '--' }}</span>
                    </div>
                    <div v-if="coinExtInfo.athDate" class="cip-item">
                      <span class="cip-lbl">{{ t('futuresPage.coinInfo.athDate') }}</span>
                      <span class="cip-val">{{ coinExtInfo.athDate }}</span>
                    </div>
                    <div v-if="coinExtInfo.whitepaper" class="cip-item cip-item--full">
                      <span class="cip-lbl">{{ t('futuresPage.coinInfo.whitepaper') }}</span>
                      <a :href="coinExtInfo.whitepaper" target="_blank" class="cip-link">{{ coinExtInfo.whitepaper }}</a>
                    </div>
                  </div>
                </template>
                <div v-else class="cip-empty">
                  <span>{{ t('futuresPage.coinInfo.empty') }}</span>
                  <button class="cip-retry" @click="store.fetchCoinExtInfo()">{{ t('futuresPage.coinInfo.reload') }}</button>
                </div>
              </div>

              <!-- 历史资金费率面板 -->
              <div v-show="subNavTab === 'funding-rate'" class="fr-panel">
                <!-- 左右分栏 -->
                <div class="fr-body">
                <!-- 左侧：info bar + 折线图 -->
                <div class="fr-left">
                <div class="fr-info-bar">
                  <div class="fr-info-item">
                    <span class="fr-info-lbl">{{ t('futuresPage.fundingPanel.rate') }}</span>
                    <span class="fr-info-val" :class="(frCurrentRate ?? 0) < 0 ? 'dn' : 'up'">
                      {{ frCurrentRateStr }}
                    </span>
                  </div>
                  <div class="fr-info-item">
                    <span class="fr-info-lbl">{{ t('futuresPage.fundingPanel.period') }}</span>
                    <span class="fr-info-val">{{ frPeriodLabel }}</span>
                  </div>
                  <div class="fr-info-item">
                    <span class="fr-info-lbl">{{ t('futuresPage.fundingPanel.nextCountdown') }}</span>
                    <span class="fr-info-val">{{ fundingCountdown }}</span>
                  </div>
                </div>
                <div class="fr-chart-wrap">
                  <div v-if="!frChart" class="fr-chart-empty">{{ t('futuresPage.fundingPanel.noChart') }}</div>
                  <svg v-else
                    ref="frChartRef"
                    :viewBox="`0 0 ${600} ${200}`"
                    preserveAspectRatio="none"
                    class="fr-chart-svg"
                    @mousemove="onFrMouseMove"
                    @mouseleave="onFrMouseLeave"
                  >
                    <defs>
                      <linearGradient id="frAreaGrad" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#4a8fff" stop-opacity="0.22"/>
                        <stop offset="100%" stop-color="#4a8fff" stop-opacity="0"/>
                      </linearGradient>
                    </defs>
                    <!-- 水平网格线 -->
                    <line v-for="(y, i) in frChart.hGrid" :key="`hg${i}`"
                      :x1="FR_PL" :y1="y.toFixed(1)" :x2="FR_PL+FR_PW" :y2="y.toFixed(1)"
                      stroke="rgba(255,255,255,0.07)" stroke-width="0.5"/>
                    <!-- 零轴虚线 -->
                    <line v-if="frChart.showZero"
                      :x1="FR_PL" :y1="frChart.zeroY.toFixed(1)"
                      :x2="FR_PL+FR_PW" :y2="frChart.zeroY.toFixed(1)"
                      stroke="rgba(255,255,255,0.3)" stroke-width="0.8" stroke-dasharray="4,4"/>
                    <!-- 渐变填充 -->
                    <path :d="frChart.areaPath" fill="url(#frAreaGrad)"/>
                    <!-- 折线 -->
                    <path :d="frChart.linePath" fill="none" stroke="#4a8fff" stroke-width="1.5"/>
                    <!-- X轴标签 -->
                    <text v-for="(lb, i) in frChart.xLabels" :key="`xl${i}`"
                      :x="lb.x" :y="FR_PT+FR_PH+18"
                      text-anchor="middle" font-size="9" fill="#6a7182">{{ lb.label }}</text>
                    <!-- Y轴标签 -->
                    <text v-for="(lb, i) in frChart.yLabels" :key="`yl${i}`"
                      :x="FR_PL+FR_PW+4" :y="lb.y+3"
                      text-anchor="start" font-size="9" fill="#6a7182">{{ lb.label }}</text>
                    <!-- 悬停十字线 + 圆点 + tooltip -->
                    <template v-if="frHoverIdx >= 0 && frChart.pts[frHoverIdx]">
                      <line
                        :x1="frChart.pts[frHoverIdx].x.toFixed(1)" :y1="FR_PT"
                        :x2="frChart.pts[frHoverIdx].x.toFixed(1)" :y2="FR_PT+FR_PH"
                        stroke="rgba(255,255,255,0.4)" stroke-width="0.5"/>
                      <circle
                        :cx="frChart.pts[frHoverIdx].x.toFixed(1)"
                        :cy="frChart.pts[frHoverIdx].y.toFixed(1)"
                        r="3.5" fill="#4a8fff" stroke="white" stroke-width="1.5"/>
                      <!-- tooltip box -->
                      <g :transform="`translate(${Math.min(frChart.pts[frHoverIdx].x + 8, FR_W - 120)},${Math.max(frChart.pts[frHoverIdx].y - 32, FR_PT)})`">
                        <rect x="0" y="0" width="112" height="36" rx="4" fill="#1a2035" opacity="0.9"/>
                        <text x="8" y="14" font-size="10" fill="#9ca3b0">{{ frFmtDatetime(frChart.pts[frHoverIdx].time) }}</text>
                        <text x="8" y="28" font-size="11" font-weight="600"
                          :fill="frChart.pts[frHoverIdx].rate >= 0 ? '#02c076' : '#f6465d'">
                          {{ (frChart.pts[frHoverIdx].rate >= 0 ? '+' : '') + (frChart.pts[frHoverIdx].rate * 100).toFixed(4) + '%' }}
                        </text>
                      </g>
                    </template>
                  </svg>
                </div><!-- /fr-chart-wrap -->
                </div><!-- /fr-left -->

                <!-- 历史列表 -->
                <div ref="frListRef" class="fr-list">
                  <div class="fr-head">
                    <span>{{ t('futuresPage.table.settlementTime') }}</span><span class="tr">{{ t('futuresPage.table.fundingRate') }}</span>
                  </div>
                  <div v-if="!frItems.length" class="fr-empty">{{ t('futuresPage.fundingPanel.empty7d') }}</div>
                  <template v-else>
                    <div v-for="(item, i) in frVisibleItems" :key="i" class="fr-row">
                      <span>{{ frFmtDatetime(item.time) }}</span>
                      <span class="tr" :class="item.rate < 0 ? 'dn' : 'up'">
                        {{ (item.rate >= 0 ? '+' : '') + (item.rate * 100).toFixed(4) + '%' }}
                      </span>
                    </div>
                  </template>
                </div>
                </div><!-- /fr-body -->
              </div><!-- /fr-panel -->

              </div><!-- /chart-area-rel -->
            </div>
          </div>

          <!-- COL 2: Order book -->
          <div ref="bookColRef" class="book-col">
            <div ref="bookTabsRef" class="book-hd-tabs">
              <button class="book-tab" :class="{ act: bookTab === 'orderbook' }" @click="bookTab = 'orderbook'">{{ t('futuresPage.book.orderBook') }}</button>
              <button class="book-tab" :class="{ act: bookTab === 'trades' }" @click="bookTab = 'trades'">{{ t('futuresPage.book.trades') }}</button>
            </div>
            <div ref="bookCtrlRef" class="book-hd-ctrl">
              <div v-if="bookTab === 'orderbook'" class="book-layouts">
                <button class="bl-btn" :class="{ act: bookLayout === 'full' }" @click="bookLayout = 'full'">
                  <svg width="16" height="14" viewBox="0 0 16 14">
                    <rect x="1" y="1" width="6" height="2" rx="0.5" fill="#f6465d"/>
                    <rect x="1" y="4.5" width="6" height="2" rx="0.5" fill="#f6465d"/>
                    <rect x="9" y="1" width="6" height="2" rx="0.5" fill="currentColor" opacity="0.3"/>
                    <rect x="9" y="4.5" width="6" height="2" rx="0.5" fill="currentColor" opacity="0.3"/>
                    <rect x="1" y="8" width="6" height="2" rx="0.5" fill="#02c076"/>
                    <rect x="1" y="11" width="6" height="2" rx="0.5" fill="#02c076"/>
                    <rect x="9" y="8" width="6" height="2" rx="0.5" fill="currentColor" opacity="0.3"/>
                    <rect x="9" y="11" width="6" height="2" rx="0.5" fill="currentColor" opacity="0.3"/>
                  </svg>
                </button>
                <button class="bl-btn" :class="{ act: bookLayout === 'asks' }" @click="bookLayout = 'asks'">
                  <svg width="16" height="14" viewBox="0 0 16 14">
                    <rect x="1" y="1" width="14" height="2" rx="0.5" fill="#f6465d"/>
                    <rect x="1" y="4.5" width="14" height="2" rx="0.5" fill="#f6465d"/>
                    <rect x="1" y="8" width="14" height="2" rx="0.5" fill="currentColor" opacity="0.2"/>
                    <rect x="1" y="11" width="14" height="2" rx="0.5" fill="currentColor" opacity="0.2"/>
                  </svg>
                </button>
                <button class="bl-btn" :class="{ act: bookLayout === 'bids' }" @click="bookLayout = 'bids'">
                  <svg width="16" height="14" viewBox="0 0 16 14">
                    <rect x="1" y="1" width="14" height="2" rx="0.5" fill="currentColor" opacity="0.2"/>
                    <rect x="1" y="4.5" width="14" height="2" rx="0.5" fill="currentColor" opacity="0.2"/>
                    <rect x="1" y="8" width="14" height="2" rx="0.5" fill="#02c076"/>
                    <rect x="1" y="11" width="14" height="2" rx="0.5" fill="#02c076"/>
                  </svg>
                </button>
              </div>
            </div>

            <template v-if="bookTab === 'orderbook'">
              <div ref="bookHeadRef" class="book-col-head">
                <span>{{ t('futuresPage.book.priceQuote') }}</span>
                <span class="tr">{{ t('futuresPage.book.amountBase', { base: baseCurrency }) }}</span>
                <span class="tr">{{ t('futuresPage.book.totalBase', { base: baseCurrency }) }}</span>
              </div>

              <div v-if="bookLayout !== 'bids'" class="book-asks">
                <div v-for="(item, i) in displayAskItems" :key="`a${i}`" class="book-row ask-row" @click="onBookClick(item.price)">
                  <div class="br-depth br-depth--ask" :style="{ width: barWidth(displayAskItems, item.total) }"/>
                  <span class="br-p dn">{{ fmtPrice(item.price) }}</span>
                  <span class="br-a">{{ parseFloat(item.amount).toFixed(4) }}</span>
                  <span class="br-t">{{ parseFloat(item.total).toFixed(2) }}</span>
                </div>
              </div>

              <div v-if="bookLayout === 'full'" ref="bookMidRef" class="book-mid" :class="tickPriceUp ? 'up' : 'dn'">
                <span class="bm-price">{{ bookMidPrice }} USDT</span>
                <svg v-if="tickPriceUp" width="12" height="12" viewBox="0 0 12 12" fill="currentColor"><path d="M6 1l5 9H1z"/></svg>
                <svg v-else width="12" height="12" viewBox="0 0 12 12" fill="currentColor"><path d="M6 11L1 2h10z"/></svg>
              </div>

              <div v-if="bookLayout !== 'asks'" class="book-bids">
                <div v-for="(item, i) in displayBidItems" :key="`b${i}`" class="book-row bid-row" @click="onBookClick(item.price)">
                  <div class="br-depth br-depth--bid" :style="{ width: barWidth(displayBidItems, item.total) }"/>
                  <span class="br-p up">{{ fmtPrice(item.price) }}</span>
                  <span class="br-a">{{ parseFloat(item.amount).toFixed(4) }}</span>
                  <span class="br-t">{{ parseFloat(item.total).toFixed(2) }}</span>
                </div>
              </div>

              <div ref="bsRatioRef" class="bs-ratio">
                <div class="bs-bar">
                  <div class="bs-b" :style="{ width: bsRatio.buy + '%' }"/>
                  <div class="bs-s" :style="{ width: bsRatio.sell + '%' }"/>
                </div>
                <div class="bs-lbl">
                  <span class="up">{{ t('futuresPage.book.buyRatio', { n: bsRatio.buy }) }}</span>
                  <span class="dn">{{ t('futuresPage.book.sellRatio', { n: bsRatio.sell }) }}</span>
                </div>
              </div>
            </template>

            <template v-else>
              <div class="book-col-head">
                <span>{{ t('futuresPage.book.priceQuote') }}</span>
                <span class="tr">{{ t('futuresPage.book.amountBase', { base: baseCurrency }) }}</span>
                <span class="tr">{{ t('futuresPage.book.time') }}</span>
              </div>
              <div v-if="!recentTrades.length" class="empty-tip">{{ t('futuresPage.book.noTrades') }}</div>
              <div v-else class="trades-list">
                <div v-for="(tr, i) in recentTrades.slice(0, 12)" :key="i" class="trade-row">
                  <span :class="tr.isSell ? 'dn' : 'up'">{{ fmtPrice(tr.price) }}</span>
                  <span class="tr">{{ parseFloat(tr.qty).toFixed(4) }}</span>
                  <span class="tr trade-time">{{ fmtTimeShort(tr.time) }}</span>
                </div>
              </div>
            </template>
          </div>
        </div>
      </div>

      <!-- RIGHT: Trading panel -->
      <div class="trade-panel" :class="{ 'mobile-show': isMobile && mobileTab === 'trade' }" @click.stop>

        <!-- Margin mode + leverage -->
        <div class="tp-hd">
          <div class="margin-wrap">
            <button class="margin-btn" :disabled="isSwitchingMode" @click.stop="showMarginDrop = !showMarginDrop">
              {{ marginModeLabel }}
              <svg v-if="!isSwitchingMode" width="8" height="5" viewBox="0 0 8 5"><path d="M1 1l3 3 3-3" stroke="currentColor" stroke-width="1.2" fill="none"/></svg>
              <span v-else class="spin-sm"/>
            </button>
            <Transition name="pop">
              <div v-if="showMarginDrop" class="margin-drop" @click.stop>
                <button v-for="m in MARGIN_MODES" :key="m.value"
                  class="mdrop-item" :class="{ act: marginMode === m.value }"
                  @click="selectMarginMode(m.value)">{{ m.label }}</button>
              </div>
            </Transition>
          </div>
          <div class="tp-hd-sp"/>
          <div class="lev-wrap">
            <button class="lev-btn" @click.stop="showLeveragePanel = !showLeveragePanel">
              {{ leverage }}x
              <svg width="8" height="5" viewBox="0 0 8 5"><path d="M1 1l3 3 3-3" stroke="currentColor" stroke-width="1.2" fill="none"/></svg>
            </button>
            <Transition name="pop">
              <div v-if="showLeveragePanel" class="lev-panel" @click.stop>
                <div class="lp-title">{{ t('futuresPage.leverage.adjustTitle') }}</div>
                <!-- 固定档位模式：只显示 pill 选择 -->
                <template v-if="isDiscreteLeverage">
                  <div class="lp-pills lp-pills--full">
                    <button v-for="lv in leverageOptions" :key="lv"
                      class="lp-pill" :class="{ act: leverageDraft === lv }"
                      @click="leverageDraft = lv">{{ lv }}x</button>
                  </div>
                </template>
                <!-- 区间任意值模式：步进 + 滑块 -->
                <template v-else>
                  <div class="lp-stepper">
                    <button class="lp-step-btn" :disabled="leverageDraft <= leverageMin" @click="leverageStep(-1)">-</button>
                    <div class="lp-step-val">{{ leverageDraft }}x</div>
                    <button class="lp-step-btn" :disabled="leverageDraft >= leverageMax" @click="leverageStep(1)">+</button>
                  </div>
                  <input type="range" class="lp-range"
                    :min="leverageMin" :max="leverageMax" step="1"
                    v-model.number="leverageDraft"/>
                  <div class="lp-range-ticks">
                    <span>{{ leverageMin }}x</span>
                    <span>{{ leverageMax }}x</span>
                  </div>
                </template>
                <div class="lp-actions">
                  <button class="lp-cancel" @click="showLeveragePanel = false">{{ t('common.cancel') }}</button>
                  <button class="lp-confirm" @click="confirmLeverage">{{ t('common.confirm') }}</button>
                </div>
              </div>
            </Transition>
          </div>
        </div>

        <!-- ══ OPEN PANEL ══ -->
        <!-- Order type -->
        <div class="ot-row">
          <button class="ot-btn" :class="{ act: orderType === 'limit' }" @click="orderType = 'limit'">{{ t('futuresPage.order.limit') }}</button>
          <button class="ot-btn" :class="{ act: orderType === 'market' }" @click="orderType = 'market'">{{ t('futuresPage.order.market') }}</button>
          <div class="ot-cond-wrap" @click.stop>
            <button class="ot-btn ot-cond" :class="{ act: orderType === 'conditional' }"
              @click="orderType = 'conditional'; showCondDrop = !showCondDrop">
              {{ orderType === 'conditional'
                ? (conditionalSubType === 'market' ? t('futuresPage.order.condMarket') : t('futuresPage.order.condLimit'))
                : t('futuresPage.order.conditional')
              }}
              <svg width="7" height="5" viewBox="0 0 8 5"><path d="M1 1l3 3 3-3" stroke="currentColor" stroke-width="1.2" fill="none"/></svg>
            </button>
            <div v-if="showCondDrop" class="cond-drop">
              <div class="cond-drop-item" :class="{ act: conditionalSubType === 'market' }"
                @mousedown.prevent="conditionalSubType = 'market'; showCondDrop = false">{{ t('futuresPage.order.condMarket') }}</div>
              <div class="cond-drop-item" :class="{ act: conditionalSubType === 'limit' }"
                @mousedown.prevent="conditionalSubType = 'limit'; showCondDrop = false">{{ t('futuresPage.order.condLimit') }}</div>
            </div>
          </div>
          <button class="ot-info">
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
          </button>
        </div>

        <div class="tp-body">
          <!-- Available -->
          <div class="avail-row">
            <span class="avail-lbl">{{ t('futuresPage.order.available') }}</span>
            <span class="avail-val">{{ trunc2(swapAvailBalance) }} {{ quoteCurrency }}</span>
            <div class="avail-acts">
              <button class="avail-act" :title="t('futuresPage.order.transferTitle')" @click.stop="showTransfer = true">
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 8h14M5 8l4-4M5 8l4 4M19 16H5M19 16l-4-4M19 16l-4 4"/></svg>
              </button>
            </div>
          </div>

          <div v-if="isLoggedIn && symbolInfo" class="avail-row max-open-row">
            <span class="avail-lbl">{{ t('futuresPage.order.maxOpen') }}</span>
            <span class="avail-val">{{ maxOpenAmountDisplay }} {{ amountUnit === 'base' ? baseCurrency : amountUnitLabel }}</span>
          </div>

          <!-- Trigger price (条件委托) -->
          <div v-if="orderType === 'conditional'" class="inp-block">
            <div class="inp-wrap">
              <span class="inp-lbl">{{ t('futuresPage.order.triggerPriceLabel', { q: quoteCurrency }) }}</span>
              <div class="inp-row">
                <input v-model="triggerPrice" type="number" class="inp" :placeholder="t('futuresPage.placeholders.triggerPrice')"/>
                <button class="inp-quick" @click="triggerPrice = ticker?.close ?? ''">{{ t('futuresPage.order.latestBtn') }}</button>
              </div>
            </div>
          </div>

          <!-- Price input -->
          <div class="inp-block">
            <div v-if="orderType === 'limit' || (orderType === 'conditional' && conditionalSubType === 'limit')" class="inp-wrap">
              <span class="inp-lbl">{{ t('futuresPage.order.priceLabel', { q: quoteCurrency }) }}</span>
              <div class="inp-row">
                <input v-model="entrustPrice" type="number" class="inp" :placeholder="t('futuresPage.placeholders.price')"/>
                <button class="inp-quick" @click="setLatestPrice">{{ t('futuresPage.order.latestBtn') }}</button>
              </div>
            </div>
            <div v-else-if="orderType === 'market'" class="inp-market">{{ t('futuresPage.order.marketExe') }}</div>
            <div v-else-if="orderType === 'conditional' && conditionalSubType === 'market'" class="inp-market">{{ t('futuresPage.order.condMarketExe') }}</div>
          </div>

          <!-- Volume input -->
          <div class="inp-block">
            <div class="inp-wrap">
              <div class="inp-lbl-row">
                <span class="inp-lbl">{{ t('futuresPage.order.qtyLabel', { u: amountUnitLabel }) }}</span>
                <div class="vol-unit-wrap" @click.stop>
                  <button class="vol-unit-btn" @click="showAmountUnitDrop = !showAmountUnitDrop">
                    {{ amountUnitLabel }}
                    <svg width="7" height="5" viewBox="0 0 8 5"><path d="M1 1l3 3 3-3" stroke="currentColor" stroke-width="1.2" fill="none"/></svg>
                  </button>
                  <Transition name="pop">
                    <div v-if="showAmountUnitDrop" class="vol-unit-drop">
                      <button v-for="u in AMOUNT_UNITS" :key="u.value"
                        class="vud-item" :class="{ act: amountUnit === u.value }"
                        @click="selectAmountUnit(u.value)">
                        {{ u.value === 'base' ? baseCurrency : u.label }}
                      </button>
                    </div>
                  </Transition>
                </div>
              </div>
              <input v-model="volume" type="number" class="inp" :placeholder="t('futuresPage.placeholders.amount')"/>
            </div>
          </div>

          <!-- PCT buttons (replaces slider) -->
          <div class="pct-btns">
            <button v-for="p in [25,50,75,100]" :key="p" class="pct-btn" @click="setPctSlider(p)">{{ p }}%</button>
          </div>

          <p v-if="error" class="trade-err">{{ error }}</p>

          <!-- Submit buttons -->
          <div v-if="isLoggedIn" class="submit-row">
            <button class="sub-btn sub-buy" :disabled="orderLoading || !volume"
              @click="side = 'buy'; submitOrder()">
              <span v-if="!orderLoading || side !== 'buy'">{{ t('futuresPage.order.openLong') }}</span>
              <span v-else class="spin-sm"/>
            </button>
            <button class="sub-btn sub-sell" :disabled="orderLoading || !volume"
              @click="side = 'sell'; submitOrder()">
              <span v-if="!orderLoading || side !== 'sell'">{{ t('futuresPage.order.openShort') }}</span>
              <span v-else class="spin-sm"/>
            </button>
          </div>
          <div v-else class="auth-btns">
            <router-link to="/register" class="auth-btn auth-register">{{ t('header.register') }}</router-link>
            <router-link to="/login" class="auth-btn auth-login">{{ t('header.login') }}</router-link>
          </div>

          <!-- Contract info -->
          <div v-if="symbolInfo" class="contract-info">
            <div class="ci-title">{{ t('futuresPage.order.contractInfoTitle') }}</div>
            <div class="ci-row"><span>{{ t('futuresPage.order.indexSource') }}</span><span>Huobi,Binance,Okex</span></div>
            <div class="ci-row"><span>{{ t('futuresPage.order.marginCoin') }}</span><span>{{ quoteCurrency }}</span></div>
            <div class="ci-row"><span>{{ t('futuresPage.order.maxLeverage') }}</span><span>{{ symbolInfo.leverage.split(',').pop() }}x</span></div>
            <div class="ci-row"><span>{{ t('futuresPage.order.maintMargin') }}</span><span>{{ (parseFloat(symbolInfo.maintenanceMarginRate) * 100).toFixed(2) }}%</span></div>
            <div class="ci-row"><span>{{ t('futuresPage.order.openFee') }}</span><span>{{ (parseFloat(symbolInfo.openFee) * 100).toFixed(3) }}%</span></div>
            <div class="ci-row"><span>{{ t('futuresPage.order.closeFee') }}</span><span>{{ (parseFloat(symbolInfo.closeFee) * 100).toFixed(3) }}%</span></div>
            <div v-if="symbolInfo.shareNumber" class="ci-row"><span>{{ t('futuresPage.order.contractValue') }}</span><span>{{ symbolInfo.shareNumber }} {{ quoteCurrency }}</span></div>
            <div class="ci-row"><span>{{ t('futuresPage.order.minOrder') }}</span><span>{{ symbolInfo.minShare }} {{ baseCurrency }}</span></div>
            <div class="ci-row"><span>{{ t('futuresPage.order.maxOrder') }}</span><span>{{ symbolInfo.maxShare }} {{ baseCurrency }}</span></div>
          </div>
        </div>

      </div>

      <!-- ═══ BOTTOM AREA (inside page-body grid col 1) ═══ -->
      <div v-if="!loading" class="bottom-area">
      <div class="btab-bar">
        <button class="btab" :class="{ act: activeTab === 'positions' }" @click="activeTab = 'positions'">
          {{ t('futuresPage.bottom.positions') }} <span class="badge">{{ positions.length }}</span>
        </button>
        <button class="btab" :class="{ act: activeTab === 'orders' }" @click="activeTab = 'orders'">
          {{ t('futuresPage.bottom.openOrders') }} <span class="badge">{{ currentOrders.length }}</span>
        </button>
        <button class="btab" :class="{ act: activeTab === 'history-orders' }" @click="activeTab = 'history-orders'; store.fetchHistoryOrders(true)">{{ t('futuresPage.bottom.historyOrders') }}</button>
        <button class="btab" :class="{ act: activeTab === 'history-positions' }" @click="activeTab = 'history-positions'; store.fetchHistoryPositions(true)">{{ t('futuresPage.bottom.historyPositions') }}</button>
        <button class="btab" :class="{ act: activeTab === 'trades' }" @click="activeTab = 'trades'; store.fetchHistoryOrders(true)">{{ t('futuresPage.bottom.tradeDetails') }}</button>
        <button class="btab" :class="{ act: activeTab === 'funding' }" @click="activeTab = 'funding'; store.fetchFundingRecords(true)">{{ t('futuresPage.bottom.fundingFlow') }}</button>
        <button class="btab" :class="{ act: activeTab === 'assets' }" @click="activeTab = 'assets'">{{ t('futuresPage.bottom.assets') }}</button>
        <div class="btab-sp"/>
        <button v-if="activeTab === 'orders'" class="close-all" :disabled="orderLoading || !currentOrders.length" @click="cancelAllOrders">{{ t('futuresPage.bottom.cancelAllOrders') }}</button>
        <button v-else class="close-all" :disabled="orderLoading || !positions.length" @click="confirmCloseAll">{{ t('futuresPage.bottom.closeAllPositions') }}</button>
      </div>

      <div ref="tabScrollRef" class="tab-scroll-area" @scroll.passive="onTabScroll">

      <template v-if="activeTab === 'positions'">
        <div v-if="!positions.length" class="empty-tip">
          <template v-if="!isLoggedIn">
            <router-link to="/login" class="tip-link">{{ t('header.login') }}</router-link>
            {{ t('futuresPage.bottom.loginPromptOr') }}
            <router-link to="/register" class="tip-link">{{ t('futuresPage.bottom.registerNow') }}</router-link>
            {{ t('futuresPage.bottom.startTrading') }}
          </template>
          <template v-else>{{ t('futuresPage.empty.noPositions') }}</template>
        </div>
        <div v-else class="data-tbl">
          <div class="dt-head dt-head--pos">
            <span>{{ t('futuresPage.table.contract') }}</span><span>{{ t('futuresPage.table.dirLeverage') }}</span><span>{{ t('futuresPage.table.openPrice') }}</span><span>{{ t('futuresPage.table.markPrice') }}</span>
            <span>{{ t('futuresPage.table.positionSize') }}</span><span>{{ t('futuresPage.table.margin') }}</span><span>{{ t('futuresPage.table.marginRate') }}</span>
            <span>{{ t('futuresPage.table.unrealizedPnl') }}</span><span>{{ t('futuresPage.table.roe') }}</span><span>{{ t('futuresPage.table.liqPrice') }}</span><span>{{ t('futuresPage.table.openTime') }}</span><span>{{ t('futuresPage.table.action') }}</span>
          </div>
          <div v-for="pos in positions" :key="pos.id" class="dt-row dt-row--pos dt-row--clickable" @click="openPosDetail(pos)">
            <span class="fw6">{{ pos.coin?.symbol?.replace('/', '-') ?? '--' }}</span>
            <span :class="pos.direction === 'BUY' ? 'up' : 'dn'">{{ dirLabel(pos.direction) }} {{ pos.leverage }}x</span>
            <span>{{ fmtPrice(pos.openPrice) }}</span>
            <span>{{ fmtPrice(pos.currentPrice) }}</span>
            <span>{{ pos.currentPosition }}</span>
            <span>{{ parseFloat(pos.currentMargin || '0').toFixed(4) }}</span>
            <span>{{ parseFloat(pos.marginRate || '0') > 0 ? parseFloat(pos.marginRate).toFixed(2) + '%' : '--' }}</span>
            <span :class="calcLivePnl(pos) >= 0 ? 'up' : 'dn'">
              {{ calcLivePnl(pos) >= 0 ? '+' : '' }}{{ calcLivePnl(pos).toFixed(4) }}
            </span>
            <span :class="calcLivePnlPct(pos) >= 0 ? 'up' : 'dn'">
              {{ calcLivePnlPct(pos) >= 0 ? '+' : '' }}{{ calcLivePnlPct(pos).toFixed(2) }}%
            </span>
            <span :class="parseFloat(pos.estimatedBlastPrice) > 0 ? 'dn' : ''">{{ parseFloat(pos.estimatedBlastPrice) > 0 ? fmtPrice(pos.estimatedBlastPrice) : '--' }}</span>
            <span>{{ fmtTime(pos.openTime) }}</span>
            <span class="op-col">
              <button class="act-btn act-btn--sec" :disabled="orderLoading" @click.stop="openTpSlModal(pos.id)">{{ t('futuresPage.actions.tpSl') }}</button>
              <button class="act-btn act-btn--sec" :disabled="orderLoading" @click.stop="reversePosition(pos)">{{ t('futuresPage.actions.reverse') }}</button>
              <button class="act-btn" :disabled="orderLoading" @click.stop="openCloseSheet(pos)">{{ t('futuresPage.actions.close') }}</button>
              <button class="act-btn act-btn--market" :disabled="orderLoading" @click.stop="store.closePosition(pos.id, pos.availablePosition)">{{ t('futuresPage.actions.marketCloseAll') }}</button>
            </span>
          </div>
        </div>
      </template>

      <template v-if="activeTab === 'orders'">
        <div v-if="!currentOrders.length" class="empty-tip">{{ t('futuresPage.empty.noOrders') }}</div>
        <div v-else class="data-tbl">
          <div class="dt-head dt-head--ord">
            <span>{{ t('futuresPage.table.contract') }}</span><span>{{ t('futuresPage.table.dirLeverage') }}</span><span>{{ t('futuresPage.table.type') }}</span><span>{{ t('futuresPage.table.orderPrice') }}</span><span>{{ t('futuresPage.table.triggerPrice') }}</span>
            <span>{{ t('futuresPage.table.orderAmt') }}</span><span>{{ t('futuresPage.table.filled') }}</span><span>{{ t('futuresPage.table.tpPrice') }}</span><span>{{ t('futuresPage.table.slPrice') }}</span><span>{{ t('futuresPage.table.time') }}</span><span>{{ t('futuresPage.table.action') }}</span>
          </div>
          <div v-for="ord in currentOrders" :key="ord.id" class="dt-row dt-row--ord dt-row--clickable" @click="openOrdDetail(ord)">
            <span class="fw6">{{ ord.symbol?.replace('/', '-') ?? '--' }}</span>
            <span :class="orderDirClass(ord.direction, ord.entrustType)">
              {{ orderDirLabel(ord.direction, ord.entrustType) }} {{ ord.leverage }}x
            </span>
            <span>{{ typeLabel(ord.openType ?? ord.type) }}</span>
            <span>{{ orderPriceDisplay(ord.entrustPrice, ord.openType ?? 0) }}</span>
            <span>{{ parseFloat(ord.triggerPrice) > 0 ? fmtPrice(ord.triggerPrice) : '--' }}</span>
            <span>{{ ord.volume }}</span>
            <span>{{ ord.tradedVolume }}</span>
            <span class="up">{{ ord.profitPrice && parseFloat(String(ord.profitPrice)) > 0 ? fmtPrice(String(ord.profitPrice)) : '--' }}</span>
            <span class="dn">{{ ord.lossPrice && parseFloat(String(ord.lossPrice)) > 0 ? fmtPrice(String(ord.lossPrice)) : '--' }}</span>
            <span>{{ fmtTime(ord.time || ord.createTime) }}</span>
            <span class="op-col">
              <button class="act-btn" :disabled="orderLoading" @click.stop="store.cancelOrder(ord.id)">{{ t('futuresPage.actions.cancelOrder') }}</button>
              <template v-if="ord.profitPrice && parseFloat(String(ord.profitPrice)) > 0 || ord.lossPrice && parseFloat(String(ord.lossPrice)) > 0">
                <button class="act-btn act-btn--sec" :disabled="orderLoading"
                  @click.stop="openTpSlModal(ord.positionId ?? '', String(ord.profitPrice ?? ''), String(ord.lossPrice ?? ''))">{{ t('futuresPage.actions.tpSl') }}</button>
              </template>
            </span>
          </div>
        </div>
      </template>

      <!-- ── 历史仓位 ── -->
      <template v-if="activeTab === 'history-positions'">
        <div v-if="!isLoggedIn" class="empty-tip">
          <router-link to="/login" class="tip-link">{{ t('header.login') }}</router-link> {{ t('futuresPage.bottom.loginPromptOr') }}
          <router-link to="/register" class="tip-link">{{ t('futuresPage.bottom.registerNow') }}</router-link>
          {{ t('futuresPage.bottom.startTrading') }}
        </div>
        <div v-else-if="!historyPositions.length" class="empty-tip">{{ t('futuresPage.empty.noHistPositions') }}</div>
        <div v-else class="data-tbl">
          <div class="dt-head dt-head--hist">
            <span>{{ t('futuresPage.table.contract') }}</span><span>{{ t('futuresPage.table.dirLeverage') }}</span><span>{{ t('futuresPage.table.status') }}</span>
            <span>{{ t('futuresPage.table.orderAmt') }}</span><span>{{ t('futuresPage.table.avgOpenPrice') }}</span><span>{{ t('futuresPage.table.profitUsdt') }}</span>
            <span>{{ t('futuresPage.table.tradedVol') }}</span><span>{{ t('futuresPage.table.closeAvg') }}</span><span>{{ t('futuresPage.table.profitRate') }}</span>
            <span>{{ t('futuresPage.table.openTime') }}</span><span>{{ t('futuresPage.table.closeTime') }}</span>
          </div>
          <div v-for="pos in historyPositions" :key="pos.id" class="dt-row dt-row--hist dt-row--clickable" @click="openHistPosDetail(pos)">
            <span class="fw6">{{ histPosSymbol(pos) }}</span>
            <span :class="pos.direction === 'BUY' ? 'up' : 'dn'">{{ dirLabel(pos.direction) }} {{ pos.leverage }}x</span>
            <span :class="statusClass(pos.status)">
              {{ statusLabel(pos.status) }}
              <template v-if="pos.closeType > 0"><br><small style="color:#6a7182">{{ closeTypeLabel(pos.closeType) }}</small></template>
            </span>
            <span>{{ histPosTotalVol(pos) }}</span>
            <span>{{ histPosOpenPrice(pos) }}</span>
            <span :class="profitClass(String(histPosProfit(pos)))">
              {{ histPosProfit(pos) >= 0 ? '+' : '' }}{{ histPosProfit(pos).toFixed(4) }}
            </span>
            <span>{{ histPosDealVol(pos) }}</span>
            <span>{{ histPosClosePrice(pos) }}</span>
            <span :class="profitClass(String(histPosProfit(pos)))">{{ histPosProfitRate(pos) }}</span>
            <span>{{ histPosOpenTime(pos) }}</span>
            <span>{{ histPosCloseTime(pos) }}</span>
          </div>
        </div>
      </template>

      <!-- ── 历史委托 ── -->
      <template v-if="activeTab === 'history-orders'">
        <div v-if="!historyOrders.length" class="empty-tip">{{ t('futuresPage.empty.noHistOrders') }}</div>
        <div v-else class="data-tbl">
          <div class="dt-head dt-head--hist-ord">
            <span>{{ t('futuresPage.table.contract') }}</span><span>{{ t('futuresPage.table.dirLeverage') }}</span><span>{{ t('futuresPage.table.type') }}</span><span>{{ t('futuresPage.table.status') }}</span>
            <span>{{ t('futuresPage.table.orderAmt') }}</span><span>{{ t('futuresPage.table.tradedVol') }}</span><span>{{ t('futuresPage.table.avgOpenPrice') }}</span><span>{{ t('futuresPage.table.entrustPriceHist') }}</span>
            <span>{{ t('futuresPage.table.orderTime') }}</span><span>{{ t('futuresPage.table.fillTime') }}</span>
          </div>
          <div v-for="ord in historyOrders" :key="ord.id" class="dt-row dt-row--hist-ord dt-row--clickable" @click="openHistOrdDetail(ord)">
            <span class="fw6">{{ histOrdSymbol(ord) }}</span>
            <span :class="orderDirClass(ord.direction, ord.entrustType)">{{ orderDirLabel(ord.direction, ord.entrustType) }} {{ ord.leverage }}x</span>
            <span>{{ typeLabel(ord.type) }}</span>
            <span :class="statusClass(ord.status)">{{ statusLabel(ord.status) }}</span>
            <span>{{ ord.volume ?? ord.tradedVolume }}</span>
            <span>{{ ord.tradedVolume }}</span>
            <span>{{ histOrdOpenPrice(ord) }}</span>
            <span>{{ histOrdEntrustPrice(ord) }}</span>
            <span>{{ ord.createTime ? fmtTime(ord.createTime) : '--' }}</span>
            <span>{{ ord.dealTime ? fmtTime(ord.dealTime) : '--' }}</span>
          </div>
        </div>
      </template>

      <!-- ── 成交明细 ── -->
      <template v-if="activeTab === 'trades'">
        <div v-if="!historyOrders.length" class="empty-tip">{{ t('futuresPage.empty.noTradeRecords') }}</div>
        <div v-else class="data-tbl">
          <div class="dt-head dt-head--trade">
            <span>{{ t('futuresPage.table.contract') }}</span><span>{{ t('futuresPage.table.directionOpenClose') }}</span><span>{{ t('futuresPage.table.dealPrice') }}</span><span>{{ t('futuresPage.table.tradedVol') }}</span>
            <span>{{ t('futuresPage.table.fee') }}</span><span>{{ t('futuresPage.table.fillTime') }}</span>
          </div>
          <div v-for="ord in historyOrders" :key="ord.id" class="dt-row dt-row--trade">
            <span class="fw6">{{ ord.symbol?.replace('/', '-') ?? '--' }}</span>
            <span :class="histDirClass(ord.direction, ord.entrustType)">
              {{ histDirLabel(ord.direction, ord.entrustType) }}
              <span class="entrust-type-tag">{{ ord.entrustType === 'OPEN' ? t('futuresPage.directions.openTag') : t('futuresPage.directions.closeTag') }}</span>
            </span>
            <span>{{ ord.tradedPrice ? fmtPrice(ord.tradedPrice) : '--' }}</span>
            <span>{{ ord.tradedVolume || '--' }}</span>
            <span class="dn">{{ ord.entrustType === 'OPEN' ? (ord.openFee ? '-' + parseFloat(ord.openFee).toFixed(4) : '--') : (ord.closeFee ? '-' + parseFloat(ord.closeFee).toFixed(4) : '--') }}</span>
            <span>{{ ord.dealTime ? fmtTime(ord.dealTime) : (ord.createTime ? fmtTime(ord.createTime) : '--') }}</span>
          </div>
        </div>
      </template>

      <!-- ── 资金流水 ── -->
      <template v-if="activeTab === 'funding'">
        <div v-if="!contractFundingRecords.length" class="empty-tip">{{ t('futuresPage.empty.noFunding') }}</div>
        <div v-else class="data-tbl">
          <div class="dt-head dt-head--funding">
            <span>{{ t('futuresPage.table.fundType') }}</span><span>{{ t('futuresPage.table.amount') }}</span><span>{{ t('futuresPage.table.coin') }}</span><span>{{ t('futuresPage.table.time') }}</span>
          </div>
          <div v-for="rec in contractFundingRecords" :key="rec.id" class="dt-row dt-row--funding">
            <span>{{ txTypeName(rec.type) }}</span>
            <span :class="parseFloat(String(rec.amount)) >= 0 ? 'up' : 'dn'">
              {{ parseFloat(String(rec.amount)) >= 0 ? '+' : '' }}{{ parseFloat(String(rec.amount)).toFixed(4) }}
            </span>
            <span>{{ formatFundingSymbol(rec.symbol) }}</span>
            <span>{{ rec.createTime ? fmtTime(new Date(String(rec.createTime).replace(' ', 'T')).getTime()) : '--' }}</span>
          </div>
        </div>
      </template>

      <!-- ── 资产 ── -->
      <template v-if="activeTab === 'assets'">
        <div v-if="!isLoggedIn" class="empty-tip">
          <router-link to="/login" class="tip-link">{{ t('header.login') }}</router-link> {{ t('futuresPage.bottom.loginPromptOr') }}
          <router-link to="/register" class="tip-link">{{ t('futuresPage.bottom.registerNow') }}</router-link>
          {{ t('futuresPage.bottom.startTrading') }}
        </div>
        <div v-else class="assets-grid">
          <div class="asset-card">
            <div class="asset-lbl">{{ t('futuresPage.table.totalBal') }}</div>
            <div class="asset-val">{{ accountInfo.totalBalance.toFixed(4) }} <span class="asset-cur">USDT</span></div>
          </div>
          <div class="asset-card">
            <div class="asset-lbl">{{ t('futuresPage.table.availMargin') }}</div>
            <div class="asset-val">{{ accountInfo.availableMargin.toFixed(4) }} <span class="asset-cur">USDT</span></div>
          </div>
          <div class="asset-card">
            <div class="asset-lbl">{{ t('futuresPage.table.usedMargin') }}</div>
            <div class="asset-val">{{ accountInfo.usedMargin.toFixed(4) }} <span class="asset-cur">USDT</span></div>
          </div>
          <div class="asset-card">
            <div class="asset-lbl">{{ t('futuresPage.table.unrealized') }}</div>
            <div class="asset-val" :class="accountInfo.unrealizedPnl >= 0 ? 'up' : 'dn'">
              {{ accountInfo.unrealizedPnl >= 0 ? '+' : '' }}{{ accountInfo.unrealizedPnl.toFixed(4) }} <span class="asset-cur">USDT</span>
            </div>
          </div>
          <div class="asset-card">
            <div class="asset-lbl">{{ t('futuresPage.table.spotWalletBal') }}</div>
            <div class="asset-val">{{ parseFloat(spotAvailBalance).toFixed(4) }} <span class="asset-cur">USDT</span></div>
          </div>
          <div class="asset-card asset-card--actions">
            <button class="asset-btn" @click="showTransfer = true">{{ t('futuresPage.table.fundTransfer') }}</button>
          </div>
        </div>
      </template>

      </div><!-- /tab-scroll-area -->
      </div><!-- /bottom-area -->

    </div><!-- /page-body -->

    <!-- ═══ 仓位详情弹窗 ═══ -->
    <Transition name="modal">
      <div v-if="showPosDetail && detailPos" class="modal-mask" @click.self="showPosDetail = false">
        <div class="modal-box detail-modal" @click.stop>
          <div class="modal-hd">
            <span class="modal-title">{{ t('futuresPage.modal.positionDetailTitle') }}</span>
            <button class="modal-close" @click="showPosDetail = false">✕</button>
          </div>
          <div class="detail-modal-body">
            <!-- 标的行 -->
            <div class="detail-header">
              <span class="detail-dir-chip" :class="detailPos.direction === 'BUY' ? 'chip--up' : 'chip--dn'">
                {{ detailPos.direction === 'BUY' ? t('futuresPage.directions.long') : t('futuresPage.directions.short') }}
              </span>
              <span class="detail-symbol">{{ detailPos.coin?.symbol?.replace('/', '-') ?? '--' }}</span>
              <span class="detail-tag">{{ posTypeLabel(detailPos.positionType, detailPos.patterns, detailPos.experienceRecordId) }}</span>
              <span class="detail-tag detail-tag--lev">{{ detailPos.leverage }}x</span>
            </div>
            <!-- 价格 -->
            <div class="detail-section">
              <div class="detail-row"><span class="detail-lbl">{{ t('futuresPage.table.openPrice') }}</span><span class="detail-val">{{ fmtPrice(detailPos.openPrice) }}</span></div>
              <div class="detail-row"><span class="detail-lbl">{{ t('futuresPage.table.markPrice') }}</span><span class="detail-val">{{ fmtPrice(detailPos.currentPrice) }}</span></div>
            </div>
            <div class="detail-section-gap"/>
            <!-- 持仓数据 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.entrustVolCoin', { base: baseCoinFromSym(detailPos.coin?.symbol ?? '') }) }}</span>
                <span class="detail-val">{{ detailPos.currentPosition }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.notionalUsdt') }}</span>
                <span class="detail-val">{{ (parseFloat(detailPos.currentPosition||'0') * parseFloat(detailPos.currentPrice||'0')).toFixed(4) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.estLiqPx') }}</span>
                <span class="detail-val" :class="parseFloat(detailPos.estimatedBlastPrice) > 0 ? 'dn' : ''">
                  {{ parseFloat(detailPos.estimatedBlastPrice) > 0 ? fmtPrice(detailPos.estimatedBlastPrice) : '--' }}
                </span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.marginUsdtBracket') }}</span>
                <span class="detail-val">{{ parseFloat(detailPos.currentMargin||'0').toFixed(4) }}</span>
              </div>
            </div>
            <div class="detail-section-gap"/>
            <!-- 盈亏 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.profitRate') }}</span>
                <span class="detail-val" :class="calcLivePnlPct(detailPos) >= 0 ? 'up' : 'dn'">
                  {{ calcLivePnlPct(detailPos) >= 0 ? '+' : '' }}{{ calcLivePnlPct(detailPos).toFixed(2) }}%
                </span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.unrealized') }}</span>
                <span class="detail-val" :class="calcLivePnl(detailPos) >= 0 ? 'up' : 'dn'">
                  {{ calcLivePnl(detailPos) >= 0 ? '+' : '' }}{{ calcLivePnl(detailPos).toFixed(4) }}
                </span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.marginRate') }}</span>
                <span class="detail-val">{{ parseFloat(detailPos.marginRate||'0') > 0 ? parseFloat(detailPos.marginRate).toFixed(2) + '%' : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.tpPrice') }}</span>
                <span class="detail-val up">{{ detailPos.profitPrice && parseFloat(detailPos.profitPrice) > 0 ? fmtPrice(detailPos.profitPrice) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.slPrice') }}</span>
                <span class="detail-val dn">{{ detailPos.lossPrice && parseFloat(detailPos.lossPrice) > 0 ? fmtPrice(detailPos.lossPrice) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.openTime') }}</span>
                <span class="detail-val">{{ fmtTime(detailPos.openTime) }}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Transition>

    <!-- ═══ 当前委托详情弹窗 ═══ -->
    <Transition name="modal">
      <div v-if="showOrdDetail && detailOrd" class="modal-mask" @click.self="showOrdDetail = false">
        <div class="modal-box detail-modal" @click.stop>
          <div class="modal-hd">
            <span class="modal-title">{{ t('futuresPage.modal.orderDetailTitle') }}</span>
            <button class="modal-close" @click="showOrdDetail = false">✕</button>
          </div>
          <div class="detail-modal-body">
            <!-- 标的行 -->
            <div class="detail-header">
              <span class="detail-dir-chip" :class="orderDirClass(detailOrd.direction, detailOrd.entrustType)">
                {{ orderDirLabel(detailOrd.direction, detailOrd.entrustType) }}
              </span>
              <span class="detail-symbol">{{ detailOrd.symbol?.replace('/', '-') ?? '--' }}</span>
              <span class="detail-tag">{{ typeLabel(detailOrd.openType ?? detailOrd.type) }}</span>
              <span class="detail-tag detail-tag--lev">{{ detailOrd.leverage }}x</span>
            </div>
            <!-- 委托信息 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.orderPrice') }}</span>
                <span class="detail-val">{{ orderPriceDisplay(detailOrd.entrustPrice, detailOrd.openType ?? 0) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.triggerPrice') }}</span>
                <span class="detail-val">{{ parseFloat(detailOrd.triggerPrice) > 0 ? fmtPrice(detailOrd.triggerPrice) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.entrustVolCoin', { base: baseCoinFromSym(detailOrd.symbol ?? '') }) }}</span>
                <span class="detail-val">{{ detailOrd.volume }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.filledVolCoin', { base: baseCoinFromSym(detailOrd.symbol ?? '') }) }}</span>
                <span class="detail-val">{{ detailOrd.tradedVolume }}</span>
              </div>
            </div>
            <div class="detail-section-gap"/>
            <!-- 止盈止损 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.tpPrice') }}</span>
                <span class="detail-val up">{{ detailOrd.profitPrice && parseFloat(String(detailOrd.profitPrice)) > 0 ? fmtPrice(String(detailOrd.profitPrice)) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.slPrice') }}</span>
                <span class="detail-val dn">{{ detailOrd.lossPrice && parseFloat(String(detailOrd.lossPrice)) > 0 ? fmtPrice(String(detailOrd.lossPrice)) : '--' }}</span>
              </div>
            </div>
            <div class="detail-section-gap"/>
            <!-- 时间与状态 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.orderTime') }}</span>
                <span class="detail-val">{{ fmtTime(detailOrd.time || detailOrd.createTime) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.status') }}</span>
                <span class="detail-val" :class="statusClass(String(detailOrd.status))">{{ statusLabel(String(detailOrd.status)) }}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Transition>

    <!-- ═══ 历史仓位详情弹窗 ═══ -->
    <Transition name="modal">
      <div v-if="showHistPosDetail && detailHistPos" class="modal-mask" @click.self="showHistPosDetail = false">
        <div class="modal-box detail-modal" @click.stop>
          <div class="modal-hd">
            <span class="modal-title">{{ t('futuresPage.modal.histPositionDetailTitle') }}</span>
            <button class="modal-close" @click="showHistPosDetail = false">✕</button>
          </div>
          <div class="detail-modal-body">
            <!-- 状态图标 -->
            <div class="detail-status-block">
              <div class="detail-status-icon">
                <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="9" y1="7" x2="15" y2="7"/><line x1="9" y1="11" x2="15" y2="11"/><line x1="9" y1="15" x2="13" y2="15"/></svg>
              </div>
              <div class="detail-status-label" :class="statusClass(detailHistPos.status)">{{ statusLabel(detailHistPos.status) }}</div>
              <div v-if="detailHistPos.closeType > 0" class="detail-close-type">{{ closeTypeLabel(detailHistPos.closeType) }}</div>
            </div>
            <!-- 基本信息 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.modal.marginSplitMode') }}</span>
                <span class="detail-val">{{ posTypeLabel(detailHistPos.positionType ?? detailHistPos.type, detailHistPos.patterns, detailHistPos.experienceRecordId) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.modal.directionLabel') }}</span>
                <span class="detail-val" :class="detailHistPos.direction === 'BUY' ? 'up' : 'dn'">{{ dirLabel(detailHistPos.direction) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.modal.entrustTypeLabel') }}</span>
                <span class="detail-val">{{ typeLabel(detailHistPos.openType ?? detailHistPos.type) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.modal.leverageLabel') }}</span>
                <span class="detail-val">{{ detailHistPos.leverage }}x</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.openTime') }}</span>
                <span class="detail-val">{{ histPosOpenTime(detailHistPos) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.closeTime') }}</span>
                <span class="detail-val">{{ histPosCloseTime(detailHistPos) }}</span>
              </div>
            </div>
            <div class="detail-section-gap"/>
            <!-- 价格信息 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.triggerUsdt') }}</span>
                <span class="detail-val">{{ detailHistPos.triggerPrice && parseFloat(detailHistPos.triggerPrice) > 0 ? fmtPrice(detailHistPos.triggerPrice) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.orderPriceUsdt') }}</span>
                <span class="detail-val">{{ detailHistPos.entrustPrice && parseFloat(detailHistPos.entrustPrice) > 0 ? fmtPrice(detailHistPos.entrustPrice) : t('futuresPage.orderTypes.marketShort') }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.openAvgUsdt') }}</span>
                <span class="detail-val">{{ histPosOpenPrice(detailHistPos) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.closeAvgUsdt') }}</span>
                <span class="detail-val">{{ histPosClosePrice(detailHistPos) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.tpUsdt') }}</span>
                <span class="detail-val up">{{ detailHistPos.profitPrice && parseFloat(detailHistPos.profitPrice) > 0 ? fmtPrice(detailHistPos.profitPrice) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.slUsdt') }}</span>
                <span class="detail-val dn">{{ detailHistPos.lossPrice && parseFloat(detailHistPos.lossPrice) > 0 ? fmtPrice(detailHistPos.lossPrice) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.marginUsdtBracket') }}</span>
                <span class="detail-val">{{ histPosPrincipal(detailHistPos) > 0 ? histPosPrincipal(detailHistPos).toFixed(2) : '--' }}</span>
              </div>
            </div>
            <div class="detail-section-gap"/>
            <!-- 盈亏信息 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.entrustVolCoin', { base: baseCoinFromSym(histPosSymbol(detailHistPos)) }) }}</span>
                <span class="detail-val">{{ histPosTotalVol(detailHistPos) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.profitBracketUsdt') }}</span>
                <span class="detail-val" :class="profitClass(String(histPosProfit(detailHistPos)))">
                  {{ histPosProfit(detailHistPos) !== 0 ? (histPosProfit(detailHistPos) >= 0 ? '+' : '') + histPosProfit(detailHistPos).toFixed(4) : '--' }}
                </span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.profitRate') }}</span>
                <span class="detail-val" :class="profitClass(String(histPosProfit(detailHistPos)))">{{ histPosProfitRate(detailHistPos) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.feeHint') }}</span>
                <span class="detail-val">{{ (parseFloat(detailHistPos.openFee||'0') + parseFloat(detailHistPos.closeFee||'0') + parseFloat(detailHistPos.commissionFee||'0')) > 0 ? (parseFloat(detailHistPos.openFee||'0') + parseFloat(detailHistPos.closeFee||'0') + parseFloat(detailHistPos.commissionFee||'0')).toFixed(4) : '--' }}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Transition>

    <!-- ═══ 历史委托详情弹窗 ═══ -->
    <Transition name="modal">
      <div v-if="showHistOrdDetail && detailHistOrd" class="modal-mask" @click.self="showHistOrdDetail = false">
        <div class="modal-box detail-modal" @click.stop>
          <div class="modal-hd">
            <span class="modal-title">{{ t('futuresPage.modal.histOrderDetailTitle') }}</span>
            <button class="modal-close" @click="showHistOrdDetail = false">✕</button>
          </div>
          <div class="detail-modal-body">
            <!-- 状态图标 -->
            <div class="detail-status-block">
              <div class="detail-status-icon">
                <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="9" y1="7" x2="15" y2="7"/><line x1="9" y1="11" x2="15" y2="11"/><line x1="9" y1="15" x2="13" y2="15"/></svg>
              </div>
              <div class="detail-status-label" :class="statusClass(detailHistOrd.status)">{{ statusLabel(detailHistOrd.status) }}</div>
            </div>
            <!-- 基本信息 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.modal.marginSplitMode') }}</span>
                <span class="detail-val">{{ posTypeLabel(detailHistOrd.positionType, detailHistOrd.patterns, detailHistOrd.experienceRecordId) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.modal.directionLabel') }}</span>
                <span class="detail-val" :class="orderDirClass(detailHistOrd.direction, detailHistOrd.entrustType)">{{ orderDirLabel(detailHistOrd.direction, detailHistOrd.entrustType) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.modal.entrustTypeLabel') }}</span>
                <span class="detail-val">{{ typeLabel(detailHistOrd.type) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.modal.leverageLabel') }}</span>
                <span class="detail-val">{{ detailHistOrd.leverage }}x</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.orderTime') }}</span>
                <span class="detail-val">{{ detailHistOrd.createTime ? fmtTime(detailHistOrd.createTime) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.fillTime') }}</span>
                <span class="detail-val">{{ detailHistOrd.dealTime ? fmtTime(detailHistOrd.dealTime) : '--' }}</span>
              </div>
            </div>
            <div class="detail-section-gap"/>
            <!-- 价格与数量 -->
            <div class="detail-section">
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.triggerUsdt') }}</span>
                <span class="detail-val">{{ detailHistOrd.triggerPrice && parseFloat(String(detailHistOrd.triggerPrice)) > 0 ? fmtPrice(String(detailHistOrd.triggerPrice)) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.orderPriceUsdt') }}</span>
                <span class="detail-val">{{ histOrdEntrustPriceDisplay(detailHistOrd) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.openAvgUsdt') }}</span>
                <span class="detail-val">{{ histOrdAvgOpenPrice(detailHistOrd) }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.closeAvgUsdt') }}</span>
                <span class="detail-val">{{ (() => { const isC = String(detailHistOrd.entrustType ?? '').toUpperCase() === 'CLOSE' || String(detailHistOrd.entrustType) === '1'; const p = isC ? detailHistOrd.tradedPrice : '0'; return parseFloat(p||'0') > 0 ? fmtPrice(p) : '--' })() }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.tpUsdt') }}</span>
                <span class="detail-val up">{{ (detailHistOrd.profitPrice ?? detailHistOrd.stopProfitPrice) && parseFloat(String(detailHistOrd.profitPrice ?? detailHistOrd.stopProfitPrice ?? '0')) > 0 ? fmtPrice(String(detailHistOrd.profitPrice ?? detailHistOrd.stopProfitPrice ?? '')) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.slUsdt') }}</span>
                <span class="detail-val dn">{{ (detailHistOrd.lossPrice ?? detailHistOrd.stopLossPrice) && parseFloat(String(detailHistOrd.lossPrice ?? detailHistOrd.stopLossPrice ?? '0')) > 0 ? fmtPrice(String(detailHistOrd.lossPrice ?? detailHistOrd.stopLossPrice ?? '')) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.marginUsdtBracket') }}</span>
                <span class="detail-val">{{ detailHistOrd.principalAmount && parseFloat(detailHistOrd.principalAmount) > 0 ? parseFloat(detailHistOrd.principalAmount).toFixed(2) : '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.entrustVolCoin', { base: baseCoinFromSym(histOrdSymbol(detailHistOrd)) }) }}</span>
                <span class="detail-val">{{ detailHistOrd.volume ?? detailHistOrd.size ?? '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.filledVolCoin', { base: baseCoinFromSym(histOrdSymbol(detailHistOrd)) }) }}</span>
                <span class="detail-val">{{ detailHistOrd.tradedVolume ?? '--' }}</span>
              </div>
              <div class="detail-row">
                <span class="detail-lbl">{{ t('futuresPage.table.feeHint') }}</span>
                <span class="detail-val">{{ (parseFloat(detailHistOrd.openFee||'0') + parseFloat(detailHistOrd.closeFee||'0')) > 0 ? (parseFloat(detailHistOrd.openFee||'0') + parseFloat(detailHistOrd.closeFee||'0')).toFixed(4) : '--' }}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Transition>

    <!-- ═══ 划转弹窗 ═══ -->
    <Transition name="modal">
      <div v-if="showTransfer" class="modal-mask" @click.self="showTransfer = false; showFromDrop = false; showToDrop = false">
        <div class="modal-box" @click.stop>
          <div class="modal-hd">
            <span class="modal-title">{{ t('spot.transferModal.title') }}</span>
            <button class="modal-close" @click="showTransfer = false">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
            </button>
          </div>

          <div class="modal-body">
            <!-- 币种 -->
            <div class="tf-field">
              <div class="tf-label">{{ t('common.coin') }}</div>
              <div class="tf-select tf-select--static">
                <span>USDT</span>
              </div>
            </div>

            <!-- 从 / 到：一侧固定资金，另一侧仅合约 / 跟单可选 -->
            <div class="tf-direction">
              <!-- 从 -->
              <div class="tf-dir-col">
                <div class="tf-dir-lbl">{{ t('common.from') }}</div>
                <div v-if="transferFrom === 'SPOT'" class="tf-select tf-select--static">
                  <span>{{ transferWalletLabel('SPOT') }}</span>
                </div>
                <div v-else class="tf-select tf-select--btn" @click="showFromDrop = !showFromDrop; showToDrop = false">
                  <span>{{ transferWalletLabel(transferFrom) }}</span>
                  <svg width="10" height="6" viewBox="0 0 10 6"><path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>
                  <div v-if="showFromDrop" class="tf-dropdown">
                    <div
                      v-for="opt in FUTURES_OTHER_WALLET_TYPES"
                      :key="opt"
                      class="tf-dropdown-item"
                      :class="{ active: transferFrom === opt }"
                      @click.stop="selectFromOther(opt)"
                    >{{ transferWalletLabel(opt) }}</div>
                  </div>
                </div>
              </div>

              <!-- 交换按钮 -->
              <button class="tf-swap" @click="swapTransferDirection">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 16V4m0 0L3 8m4-4l4 4M17 8v12m0 0l4-4m-4 4l-4-4"/></svg>
              </button>

              <!-- 到 -->
              <div class="tf-dir-col">
                <div class="tf-dir-lbl">{{ t('common.to') }}</div>
                <div
                  v-if="transferFrom === 'SWAP' || transferFrom === 'FOLLOW' || transferFrom === 'SPOT_TRADING'"
                  class="tf-select tf-select--static"
                >
                  <span>{{ transferWalletLabel('SPOT') }}</span>
                </div>
                <div v-else class="tf-select tf-select--btn" @click="showToDrop = !showToDrop; showFromDrop = false">
                  <span>{{ transferWalletLabel(transferTo) }}</span>
                  <svg width="10" height="6" viewBox="0 0 10 6"><path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>
                  <div v-if="showToDrop" class="tf-dropdown">
                    <div
                      v-for="opt in FUTURES_OTHER_WALLET_TYPES"
                      :key="opt"
                      class="tf-dropdown-item"
                      :class="{ active: transferTo === opt }"
                      @click.stop="selectToOther(opt)"
                    >{{ transferWalletLabel(opt) }}</div>
                  </div>
                </div>
              </div>
            </div>

            <!-- 划转数量 -->
            <div class="tf-field">
              <div class="tf-label">{{ t('spot.transferModal.amount') }}</div>
              <div class="tf-amount-wrap">
                <input
                  v-model="transferAmount"
                  type="number"
                  class="tf-amount-inp"
                  :placeholder="t('spot.transferModal.amountPlaceholder')"
                  :disabled="transferSubmitting"
                  @input="transferError = ''"
                />
                <div class="tf-amount-right">
                  <span class="tf-currency">USDT</span>
                  <span class="tf-sep">|</span>
                  <button class="tf-all" @click="transferAmount = transferAvail">{{ t('common.all') }}</button>
                </div>
              </div>
              <div class="tf-avail">
                {{ t('spot.transferModal.availableUsdt') }}
                <span v-if="transferLoading" class="tf-avail-loading">{{ t('common.loading') }}</span>
                <span v-else>{{ transferAvail }}</span>
              </div>
              <div v-if="transferError" class="tf-error">{{ transferError }}</div>
            </div>
          </div>

          <div class="modal-ft">
            <button class="tf-cancel" :disabled="transferSubmitting" @click="showTransfer = false">{{ t('common.cancel') }}</button>
            <button class="tf-confirm" :disabled="transferSubmitting || transferLoading" @click="confirmTransfer">
              {{ transferSubmitting ? t('spot.transferModal.transferring') : t('common.confirm') }}
            </button>
          </div>
        </div>
      </div>
    </Transition>

    <!-- ── TP/SL Modify Modal ── -->
    <Transition name="modal">
      <div v-if="showTpSlModal" class="modal-mask" @click.self="showTpSlModal = false">
        <div class="modal-box" @click.stop>
          <div class="modal-hd">
            <span class="modal-title">{{ tpSlPosition?.cutList?.length ? t('futuresPage.modal.modifyTpSl') : t('futuresPage.modal.setTpSl') }}</span>
            <button class="modal-close" @click="showTpSlModal = false">✕</button>
          </div>
          <div class="modal-body">
            <!-- 仓位信息摘要 -->
            <div class="tpsl-pos-summary">
              <span class="fw6">{{ tpSlPosition?.coin?.symbol?.replace('/', '-') }}</span>
              <span :class="tpSlPosition?.direction === 'BUY' ? 'up' : 'dn'" style="margin-left:8px">
                {{ tpSlPosition?.direction === 'BUY' ? t('futuresPage.directions.long') : t('futuresPage.directions.short') }} {{ tpSlPosition?.leverage }}x
              </span>
              <span style="margin-left:auto;color:#8a92a8;font-size:11px">{{ t('futuresPage.modal.posQtyLabel') }} {{ tpSlPosition?.currentPosition }}</span>
            </div>

            <!-- 关键价格信息 -->
            <div v-if="tpSlPosition" class="tpsl-info-rows">
              <div class="tpsl-info-row"><span>{{ t('futuresPage.table.openPrice') }}</span><span>{{ fmtPrice(tpSlPosition.openPrice) }}</span></div>
              <div class="tpsl-info-row"><span>{{ t('futuresPage.table.markPrice') }}</span><span>{{ fmtPrice(tpSlMarkPriceStr || tpSlPosition.currentPrice) }}</span></div>
              <div class="tpsl-info-row"><span>{{ t('futuresPage.table.liqPrice') }}</span><span>{{ parseFloat(tpSlPosition.estimatedBlastPrice) > 0 ? fmtPrice(tpSlPosition.estimatedBlastPrice) : '--' }}</span></div>
            </div>

            <!-- 当前止盈止损委托列表 -->
            <template v-if="tpSlPosition?.cutList?.length">
              <div class="tpsl-section-title">{{ t('futuresPage.modal.currentTpSlSection') }}</div>
              <div class="tpsl-current-list">
                <div v-for="cut in tpSlPosition.cutList" :key="cut.id" class="tpsl-current-item">
                  <div class="tpsl-current-row">
                    <span v-if="cut.profitPrice && parseFloat(String(cut.profitPrice)) > 0" class="tpsl-badge tpsl-badge--profit">{{ t('futuresPage.modal.tpBadge') }}</span>
                    <span v-if="cut.lossPrice && parseFloat(String(cut.lossPrice)) > 0" class="tpsl-badge tpsl-badge--loss">{{ t('futuresPage.modal.slBadge') }}</span>
                    <span class="tpsl-current-detail">
                      <template v-if="cut.profitPrice && parseFloat(String(cut.profitPrice)) > 0">
                        {{ t('futuresPage.modal.tpPriceWord') }}<strong>{{ fmtPrice(String(cut.profitPrice)) }}</strong>
                      </template>
                      <template v-if="cut.lossPrice && parseFloat(String(cut.lossPrice)) > 0">
                        <span style="margin:0 6px;color:#3a3e4a">|</span>
                        {{ t('futuresPage.modal.slPriceWord') }}<strong>{{ fmtPrice(String(cut.lossPrice)) }}</strong>
                      </template>
                    </span>
                    <span v-if="cut.volume" class="tpsl-current-vol">{{ t('futuresPage.modal.volLabel') }} {{ cut.volume }}</span>
                  </div>
                </div>
              </div>
            </template>

            <!-- 修改输入区 -->
            <div class="tpsl-section-title">{{ tpSlPosition?.cutList?.length ? t('futuresPage.modal.modifyToSection') : t('futuresPage.modal.setPricesSection') }}</div>
            <div class="tf-row">
              <label class="tf-label">{{ t('futuresPage.modal.tpTriggerLabel') }}<span style="color:#3a3e4a;font-weight:400">{{ t('futuresPage.modal.tpHintOptionalClear') }}</span></label>
              <input v-model="tpSlProfitPrice" type="number" class="tf-inp tf-inp--profit" :placeholder="t('futuresPage.modal.placeholderTpTrigger')" />
            </div>
            <div class="tf-row">
              <label class="tf-label">{{ t('futuresPage.modal.slTriggerLabel') }}<span style="color:#3a3e4a;font-weight:400">{{ t('futuresPage.modal.slHintOptionalClear') }}</span></label>
              <input v-model="tpSlLossPrice" type="number" class="tf-inp tf-inp--loss" :placeholder="t('futuresPage.modal.placeholderSlTrigger')" />
            </div>
            <div v-if="tpSlError" class="tf-err">{{ tpSlError }}</div>
          </div>
          <div class="modal-ft">
            <button class="modal-cancel" @click="showTpSlModal = false">{{ t('common.cancel') }}</button>
            <button class="modal-confirm" :disabled="orderLoading" @click="confirmTpSl">{{ t('common.confirm') }}</button>
          </div>
        </div>
      </div>
    </Transition>

    <!-- ── 平仓 Sheet ── -->
    <Transition name="modal">
      <div v-if="showCloseSheet && closeTargetPos" class="modal-mask" @click.self="showCloseSheet = false">
        <div class="modal-box" @click.stop>
          <div class="modal-hd">
            <span class="modal-title">{{ t('futuresPage.modal.closePositionTitlePrefix') }} · {{ closeTargetPos.coin?.symbol?.replace('/', '-') }}</span>
            <button class="modal-close" @click="showCloseSheet = false">✕</button>
          </div>
          <div class="modal-body close-sheet-stack">
            <div class="tpsl-pos-summary">
              <span :class="closeTargetPos.direction === 'BUY' ? 'up' : 'dn'">
                {{ closeTargetPos.direction === 'BUY' ? t('futuresPage.directions.long') : t('futuresPage.directions.short') }} {{ closeTargetPos.leverage }}x
              </span>
              <span style="margin-left:auto;color:#8a92a8;font-size:11px">{{ t('futuresPage.table.closeAvail') }} {{ closeTargetPos.availablePosition }} {{ closeSheetBaseCurrency }}</span>
            </div>
            <div class="tpsl-info-rows">
              <div class="tpsl-info-row"><span>{{ t('futuresPage.table.openPrice') }}</span><span>{{ fmtPrice(closeTargetPos.openPrice) }}</span></div>
              <div class="tpsl-info-row"><span>{{ t('futuresPage.table.markPrice') }}</span><span>{{ fmtPrice(closeSheetMarkPriceStr || closeTargetPos.currentPrice) }}</span></div>
            </div>

            <!-- 市价/限价切换 -->
            <div class="cs-tabs">
              <button class="cs-tab" :class="{ act: closeIsMarket }" @click="closeIsMarket = true">{{ t('futuresPage.order.market') }}</button>
              <button class="cs-tab" :class="{ act: !closeIsMarket }" @click="closeIsMarket = false">{{ t('futuresPage.order.limit') }}</button>
            </div>

            <!-- 价格 -->
            <div v-if="!closeIsMarket" class="inp-block">
              <div class="inp-wrap">
                <span class="inp-lbl">{{ t('futuresPage.table.orderPrice') }} ({{ closeSheetQuoteCurrency }})</span>
                <div class="inp-row">
                  <input v-model="closeSheetPrice" type="number" class="inp" :placeholder="t('futuresPage.placeholders.price')"/>
                  <button class="inp-quick" @click="closeSheetPrice = closeSheetMarkPriceStr || closeTargetPos.currentPrice">{{ t('futuresPage.table.markPriceBtn') }}</button>
                </div>
              </div>
            </div>
            <div v-else class="inp-block">
              <div class="inp-market">{{ t('futuresPage.order.marketExe') }}</div>
            </div>

            <!-- 数量 -->
            <div class="inp-block">
              <div class="inp-wrap">
                <span class="inp-lbl">{{ t('futuresPage.order.qtyLabel', { u: closeSheetBaseCurrency }) }}</span>
                <div class="inp-row">
                  <input v-model="closeSheetVolume" type="number" class="inp" :placeholder="t('futuresPage.placeholders.amount')"
                    @input="() => { const a = parseFloat(closeTargetPos!.availablePosition); if (a>0) closeSheetPct = Math.min(100, Math.round(parseFloat(closeSheetVolume)/a*100)) || 0 }"/>
                  <button class="inp-quick" @click="closeSheetVolume = closeTargetPos.availablePosition; closeSheetPct = 100">{{ t('common.all') }}</button>
                </div>
              </div>
            </div>

            <!-- PCT 滑块 -->
            <div class="pct-wrap">
              <div class="pct-track-wrap">
                <input type="range" v-model="closeSheetPct" min="0" max="100" step="1" class="pct-slider"/>
                <div class="pct-dots">
                  <span v-for="d in [0,25,50,75,100]" :key="d" class="pct-dot" :class="{ fill: closeSheetPct >= d }"/>
                </div>
              </div>
              <div class="pct-ticks">
                <span>0%</span><span>25%</span><span>50%</span><span>75%</span><span>100%</span>
              </div>
            </div>

            <!-- 预计盈亏 -->
            <div class="tpsl-info-rows">
              <div class="tpsl-info-row">
                <span>{{ t('futuresPage.table.estimatedPnlRow') }}</span>
                <span :class="closeSheetEstPnl >= 0 ? 'up' : 'dn'">
                  {{ closeSheetEstPnl >= 0 ? '+' : '' }}{{ closeSheetEstPnl.toFixed(4) }} {{ closeSheetQuoteCurrency }}
                </span>
              </div>
            </div>
            <div v-if="closeSheetError" class="tf-err">{{ closeSheetError }}</div>
          </div>
          <div class="modal-ft">
            <button class="modal-cancel" @click="showCloseSheet = false">{{ t('common.cancel') }}</button>
            <button class="modal-confirm" :disabled="orderLoading" @click="confirmClose">{{ t('futuresPage.modal.confirmCloseBtn') }}</button>
          </div>
        </div>
      </div>
    </Transition>

    <AppModal
      v-if="showActionConfirm"
      :title="actionConfirmTitle"
      :confirm-text="t('futuresPage.modal.ok')"
      :cancel-text="t('common.cancel')"
      :loading="actionConfirmLoading"
      @confirm="onActionConfirmOk"
      @cancel="onActionConfirmCancel"
    >
      <p class="action-confirm-msg">{{ actionConfirmMessage }}</p>
    </AppModal>

  </div><!-- /ft-page -->
</template>

<style scoped>
/* 二次确认弹窗文案 */
.action-confirm-msg {
  margin: 0;
  font-size: 14px;
  line-height: 1.55;
  color: var(--color-text-secondary, #9ca3b0);
}

/* ─── Reset / Base ─── */
* { box-sizing: border-box; }

.ft-page {
  min-height: 100vh;
  padding-top: var(--header-height, 60px);
  box-sizing: border-box;
  background: #141414;
  color: #d1d1d2;
  display: grid;
  grid-template-columns: 1fr 283px;
  grid-template-rows: 58px 420px 260px;
  column-gap: 1px;
  font-size: 13px;
  font-family: var(--font-family);
}

/* ─── Ticker bar ─── */
.ticker-bar {
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: center;
  height: 58px;
  padding: 0 12px 0 8px;
  background: #000;
  border-bottom: 1px solid #212121;
  z-index: 30;
  min-width: 0;
}

.pair-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  padding: 5px 10px 5px 6px;
  border-radius: 6px;
  position: relative;
  flex-shrink: 0;
  margin-right: 4px;
  transition: background .15s;
}
.pair-trigger:hover { background: rgba(255,255,255,0.04); }

.coin-icon {
  width: 32px; height: 32px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; overflow: hidden; background: #2a2a2a;
}
.coin-icon img { width: 100%; height: 100%; object-fit: cover; }
.coin-icon-fb {
  font-size: 13px; font-weight: 700; color: #f0b90b;
}
.pair-name-col { display: flex; flex-direction: column; gap: 1px; }
.pair-name-row { display: flex; align-items: center; gap: 4px; }
.pair-name { font-size: 18px; font-weight: 700; color: #e8ecf4; letter-spacing: 0.3px; }
.pair-sub { font-size: 10px; color: #5a6278; }
.caret { color: #6a7182; transition: transform .2s; flex-shrink: 0; }
.caret.open { transform: rotate(180deg); }

/* Pair dropdown */
.pair-drop {
  position: absolute; top: calc(100% + 6px); left: 0;
  width: 340px; background: #1d1d1d;
  border: 1px solid rgba(255,255,255,0.1); border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0,0,0,.9); overflow: hidden; z-index: 200;
}
.pair-drop-search {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px; border-bottom: 1px solid #212121;
}
.pair-search-input { flex: 1; background: none; color: #c7cce4; font-size: 13px; }
.pair-search-input::placeholder { color: #5a6278; }
.pair-list { max-height: 360px; overflow-y: auto; }
.pair-list::-webkit-scrollbar { width: 4px; }
.pair-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,.1); border-radius: 2px; }
.pair-item {
  display: grid; grid-template-columns: 22px 1fr 1fr 80px;
  align-items: center; gap: 6px; padding: 9px 14px;
  cursor: pointer; transition: background .12s;
}
.pair-item:hover, .pair-item.active { background: rgba(255,255,255,0.04); }
.pi-icon {
  width: 20px; height: 20px; border-radius: 50%; overflow: hidden;
  flex-shrink: 0; background: #2a2a2a;
  display: flex; align-items: center; justify-content: center;
}
.pi-icon img { width: 100%; height: 100%; object-fit: cover; }
.pi-icon-fb { font-size: 10px; font-weight: 700; color: #f0b90b; }
.pi-sym { font-weight: 600; font-size: 12px; }
.pi-price { font-size: 12px; color: #9298ab; text-align: right; }
.pi-chg { font-size: 12px; text-align: right; }
.pair-empty { padding: 24px; text-align: center; color: #5a6278; font-size: 13px; }

/* Ticker price */
.ticker-price-wrap {
  flex-shrink: 0; padding: 0 20px;
  border-right: 1px solid #212121;
}
.ticker-price { font-size: 24px; font-weight: 700; line-height: 1.2; font-variant-numeric: tabular-nums; }
.ticker-usd { font-size: 11px; color: #5a6278; margin-top: 2px; }

/* Ticker stats */
.ticker-stats { display: flex; align-items: center; flex: 1; overflow: hidden; padding-left: 8px; }
.ts-item { display: flex; flex-direction: column; gap: 3px; flex-shrink: 0; padding: 0 16px; }
.ts-lbl { font-size: 11px; color: #5a6278; white-space: nowrap; }
.ts-val { font-size: 12px; font-weight: 500; white-space: nowrap; color: #c7cce4; }

/* ─── Page body (display:contents so children participate in ft-page grid) ─── */
.page-body {
  display: contents;
}

/* ─── Chart area ─── */
.chart-area {
  grid-column: 1;
  grid-row: 2;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: #000;
}

/* Sub-nav */
.sub-nav {
  display: flex;
  align-items: center;
  height: 38px;
  padding: 0 8px;
  border-bottom: 1px solid #212121;
  flex-shrink: 0;
}
.snav-tab {
  padding: 0 14px; height: 100%;
  font-size: 13px; color: #6a7182;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: all .15s; white-space: nowrap;
}
.snav-tab:hover { color: #c7cce4; }
.snav-tab.act { color: #e8ecf4; border-bottom-color: #f0b90b; }

/* Loading */
.chart-loading {
  flex: 1; display: flex; align-items: center; justify-content: center;
}
.spinner {
  width: 28px; height: 28px;
  border: 2px solid rgba(255,255,255,0.07);
  border-top-color: #f0b90b; border-radius: 50%;
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Chart + book side by side */
.chart-plus-book {
  flex: 1; min-height: 0;
  display: flex;
}

/* ─── Chart column ─── */
.chart-col {
  flex: 1; min-width: 0;
  display: flex;
  border-right: 1px solid #212121;
}

/* Drawing sidebar */
.draw-side {
  width: 44px; flex-shrink: 0;
  display: flex; flex-direction: column; align-items: center;
  padding: 44px 0 8px; gap: 2px;
  border-right: 1px solid #212121;
}
.db {
  width: 34px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  color: #6a7182; border-radius: 4px; transition: all .12s; flex-shrink: 0;
}
.db:hover, .db--act { color: #e8ecf4; background: rgba(255,255,255,0.08); }
.db-sep { width: 20px; height: 1px; background: rgba(255,255,255,0.07); margin: 3px 0; }
.db-spacer { flex: 1; }

/* Chart main */
.chart-main {
  flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0;
}

/* TF bar */
.tf-bar {
  display: flex; align-items: center; gap: 1px;
  padding: 0 8px; height: 36px;
  border-bottom: 1px solid #212121;
  flex-shrink: 0;
}
.tf-btn {
  padding: 3px 7px; font-size: 12px; color: #6a7182;
  border-radius: 3px; transition: all .12s; white-space: nowrap;
}
.tf-btn:hover { color: #c7cce4; background: rgba(255,255,255,0.05); }
.tf-btn.act { color: #f0b90b; }
.tf-more {
  padding: 4px 6px; color: #6a7182; border-radius: 3px;
  display: flex; align-items: center; justify-content: center;
  transition: all .12s;
}
.tf-more:hover { color: #c7cce4; }
.tf-div { width: 1px; height: 14px; background: rgba(255,255,255,0.08); margin: 0 4px; flex-shrink: 0; }
.tf-spacer { flex: 1; }
.tf-icon {
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  color: #6a7182; border-radius: 3px; transition: all .12s; flex-shrink: 0;
}
.tf-icon:hover { color: #c7cce4; background: rgba(255,255,255,0.06); }
.tf-price-mode {
  display: flex; align-items: center; gap: 3px;
  padding: 3px 7px; font-size: 12px; color: #6a7182;
  border-radius: 3px; transition: all .12s; white-space: nowrap;
}
.tf-price-mode:hover { color: #c7cce4; }
.ma-leg { font-size: 11px; font-weight: 600; margin-right: 2px; }
.ma5 { color: #f0b90b; }
.ma10 { color: #5d9cf5; }
.ma30 { color: #e84d5b; }
.tf-ind {
  padding: 2px 6px; font-size: 11px; font-weight: 600;
  border: 1px solid rgba(255,255,255,0.1); border-radius: 3px;
  color: #6a7182; transition: all .12s; flex-shrink: 0; margin-left: 1px;
}
.tf-ind.act { color: #f0b90b; border-color: rgba(240,185,11,0.4); background: rgba(240,185,11,0.07); }

/* Chart canvas */
.chart-area-rel { flex: 1; position: relative; min-height: 0; overflow: hidden; }
/* z-index:1 使 chart-canvas-wrap 形成独立层叠上下文，内部 ohlcv-overlay(z:10)/tooltip(z:20) 被隔离，
   不会穿透到 coin-info-panel(z:2) / fr-panel(z:2) 之上 */
.chart-canvas-wrap { position: absolute; inset: 0; overflow: hidden; z-index: 1; }
.kline-canvas { width: 100%; height: 100%; }
.ohlcv-overlay {
  position: absolute; top: 4px; left: 4px; right: 0; z-index: 10;
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  font-size: 11px; color: #6a7182; pointer-events: none; padding: 2px 6px;
}
.ohlcv-sym { color: #9298ab; font-weight: 500; }
.ohlcv-item em { font-style: normal; color: #9298ab; }

/* Hover detail tooltip */
.kline-tooltip {
  position: absolute; top: 24px; z-index: 20;
  background: rgba(16, 17, 22, 0.88);
  border: 1px solid rgba(255,255,255,0.07);
  border-radius: 4px; padding: 6px 10px;
  font-size: 11px; pointer-events: none; min-width: 160px;
}
.kt-time { color: #6a7182; margin-bottom: 3px; font-size: 10.5px; }
.kt-row { display: flex; justify-content: space-between; gap: 16px; line-height: 1.75; }
.kt-lbl { color: #6a7182; }
.kt-val { color: #d1d4dc; }
.kt-c-ma5 { color: #f0b90b; }
.kt-c-ma10 { color: #5d9cf5; }
.kt-c-ma30 { color: #e84d5b; }

/* ─── Order book ─── */
.book-col {
  width: 214px; flex-shrink: 0;
  background: #000;
  display: flex; flex-direction: column; overflow: hidden;
}
.book-hd-tabs {
  display: flex; align-items: center;
  padding: 0 12px; height: 40px;
  border-bottom: 1px solid #212121;
  flex-shrink: 0;
}
.book-hd-ctrl {
  display: flex; align-items: center;
  padding: 0 12px; height: 34px;
  border-bottom: 1px solid #212121;
  flex-shrink: 0; gap: 6px;
}
.book-tab {
  padding: 0 6px; height: 100%;
  font-size: 13px; font-weight: 500; color: #45454a;
  position: relative; transition: color .12s; white-space: nowrap;
}
.book-tab:hover { color: #d1d1d2; }
.book-tab.act { color: #fff; }
.book-tab.act::after {
  content: ''; position: absolute; bottom: 0; left: 50%;
  transform: translateX(-50%); width: 16px; height: 3px;
  background: #fff; border-radius: 2px 2px 0 0;
}
.book-hd-sp { flex: 1; }
.book-layouts { display: flex; align-items: center; gap: 4px; }
.bl-btn {
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  color: #5a6278; border-radius: 3px; transition: all .12s;
}
.bl-btn:hover { background: rgba(255,255,255,0.06); color: #c7cce4; }
.bl-btn.act { background: rgba(255,255,255,0.08); color: #c7cce4; }
.prec-wrap { position: relative; margin-left: auto; }
.book-precision {
  font-size: 11px; color: #6a7182;
  padding: 3px 6px; border: 1px solid rgba(255,255,255,0.1);
  border-radius: 3px; display: flex; align-items: center; gap: 3px; transition: all .12s;
}
.book-precision:hover { color: #c7cce4; border-color: rgba(255,255,255,0.2); }
.prec-drop {
  position: absolute; top: calc(100% + 4px); right: 0;
  min-width: 80px; background: #1d1d1d;
  border: 1px solid rgba(255,255,255,0.1); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,.7); z-index: 100; overflow: hidden;
}
.pdrop-item {
  display: block; width: 100%; padding: 8px 14px;
  font-size: 12px; color: #9298ab; text-align: left; transition: all .12s;
}
.pdrop-item:hover { background: rgba(255,255,255,0.06); color: #e8ecf4; }
.pdrop-item.act { color: #fff; }
.book-col-head {
  display: grid; grid-template-columns: 1fr 1fr 1fr;
  padding: 4px 8px; font-size: 10px; color: #5a6278;
  flex-shrink: 0;
}
.book-asks, .book-bids { overflow: hidden; }
.book-row {
  position: relative; display: grid; grid-template-columns: 1fr 1fr 1fr;
  padding: 2px 8px; font-size: 11px; align-items: center; cursor: pointer;
}
.book-row:hover { background: rgba(255,255,255,0.02); }
.br-depth {
  position: absolute; top: 0; right: 0; bottom: 0;
  opacity: 0.12; pointer-events: none; transition: width .3s;
}
.br-depth--ask { background: #f6465d; }
.br-depth--bid { background: #02c076; }
.br-p { font-weight: 500; z-index: 1; font-variant-numeric: tabular-nums; }
.br-a { text-align: right; color: #7a8096; z-index: 1; font-size: 10.5px; }
.br-t { text-align: right; color: #5a6278; z-index: 1; font-size: 10.5px; }
.tr { text-align: right; }
.book-mid {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 8px; flex-shrink: 0;
}
.book-mid.up { background: rgba(2,192,118,0.06); }
.book-mid.dn { background: rgba(246,70,93,0.06); }
.bm-price {
  font-size: 16px; font-weight: 700;
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
}
.bm-mark-price {
  margin-left: auto; font-size: 11px; color: #6a7182;
  font-variant-numeric: tabular-nums;
}
.bs-ratio { margin-top: auto; padding: 6px 8px; border-top: 1px solid #212121; flex-shrink: 0; }
.bs-bar { display: flex; height: 3px; border-radius: 2px; overflow: hidden; margin-bottom: 4px; }
.bs-b { background: #02c076; transition: width .5s; }
.bs-s { background: #f6465d; transition: width .5s; }
.bs-lbl { display: flex; justify-content: space-between; font-size: 11px; }

/* ─── Trade panel ─── */
.trade-panel {
  grid-column: 2;
  grid-row: 1 / span 3;
  background: #000;
  display: flex; flex-direction: column;
  overflow-y: auto;
  min-height: 0;
}
.trade-panel::-webkit-scrollbar { width: 3px; }
.trade-panel::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }

/* 上线初期：交易禁用提示 */
/* TP header */
.tp-hd {
  display: flex; align-items: center;
  padding: 8px 12px 8px; gap: 6px;
  border-bottom: 1px solid #212121;
  position: relative;
}
.margin-wrap { position: relative; }
.margin-btn {
  display: flex; align-items: center; gap: 3px;
  font-size: 12px; color: #9298ab;
  padding: 4px 10px; border-radius: 4px;
  border: 1px solid rgba(255,255,255,0.12);
  transition: all .12s;
}
.margin-btn:hover { border-color: rgba(255,255,255,0.25); color: #c7cce4; }
.margin-drop {
  position: absolute; top: calc(100% + 4px); left: 0;
  min-width: 100px; background: #1d1d1d;
  border: 1px solid rgba(255,255,255,0.1); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,.7); z-index: 100; overflow: hidden;
}
.mdrop-item {
  display: block; width: 100%; padding: 10px 14px;
  font-size: 13px; color: #9298ab; text-align: left; transition: all .12s;
}
.mdrop-item:hover { background: rgba(255,255,255,0.06); color: #e8ecf4; }
.mdrop-item.act { color: #fff; }
.tp-hd-sp { flex: 1; }
.lev-wrap { position: relative; }
.lev-btn {
  display: flex; align-items: center; gap: 3px;
  font-size: 13px; font-weight: 700; color: #f0b90b;
  padding: 4px 10px; border-radius: 4px;
  border: 1px solid rgba(240,185,11,0.4);
  background: rgba(240,185,11,0.06);
  transition: all .12s;
}
.lev-btn:hover { background: rgba(240,185,11,0.12); }
.lev-panel {
  position: absolute; top: calc(100% + 4px); right: 0;
  width: 200px; background: #1d1d1d;
  border: 1px solid rgba(255,255,255,0.1); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,.7); z-index: 100; padding: 14px;
}
.lp-title { font-size: 12px; color: #5a6278; margin-bottom: 10px; }
.lp-pills { display: flex; flex-wrap: wrap; gap: 6px; }
.lp-pills--full { width: 100%; }
.lp-pill {
  padding: 5px 12px; font-size: 12px;
  border-radius: 4px; border: 1px solid rgba(255,255,255,0.1);
  color: #6a7182; transition: all .12s;
}
.lp-pill:hover { border-color: #f0b90b; color: #f0b90b; }
.lp-pill.act { background: rgba(240,185,11,0.1); border-color: #f0b90b; color: #f0b90b; }
.lp-stepper {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 10px;
}
.lp-step-btn {
  width: 28px; height: 28px;
  font-size: 16px; font-weight: 700; color: #c7cce4;
  background: rgba(255,255,255,0.06);
  border-radius: 4px;
  transition: all .12s;
}
.lp-step-btn:hover:not(:disabled) { background: rgba(255,255,255,0.12); }
.lp-step-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.lp-step-val {
  flex: 1; text-align: center;
  font-size: 16px; font-weight: 700; color: #f0b90b;
}
.lp-range {
  width: 100%; height: 4px; cursor: pointer;
  accent-color: #f0b90b; margin-bottom: 4px;
}
.lp-range-ticks {
  display: flex; justify-content: space-between;
  font-size: 10px; color: #5a6278; margin-bottom: 10px;
}
.lp-actions {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 8px; margin-top: 10px;
}
.lp-cancel, .lp-confirm {
  height: 32px; border-radius: 4px;
  font-size: 13px; font-weight: 600;
  transition: all .12s;
}
.lp-cancel { background: rgba(255,255,255,0.06); color: #c7cce4; }
.lp-cancel:hover { background: rgba(255,255,255,0.12); }
.lp-confirm { background: #f0b90b; color: #1a1a1a; }
.lp-confirm:hover { opacity: 0.9; }

/* Vol unit dropdown */
.vol-unit-wrap { position: relative; }
.vol-unit-drop {
  position: absolute; top: calc(100% + 4px); right: 0;
  min-width: 80px; background: #1d1d1d;
  border: 1px solid rgba(255,255,255,0.1); border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0,0,0,.7); z-index: 100; overflow: hidden;
}
.vud-item {
  display: block; width: 100%; padding: 6px 12px;
  font-size: 12px; color: #9298ab; text-align: left;
  transition: all .12s;
}
.vud-item:hover { background: rgba(255,255,255,0.06); color: #e8ecf4; }
.vud-item.act { color: #f0b90b; }

/* Close sheet tabs */
.cs-tabs {
  display: flex; gap: 6px; padding: 8px 0 4px;
}
.cs-tab {
  flex: 1; height: 30px; font-size: 13px; font-weight: 500;
  color: #8b8b8e; background: rgba(255,255,255,0.06);
  border-radius: 6px; transition: all .12s;
}
.cs-tab:hover { color: #c7cce4; }
.cs-tab.act { color: #1a1a1a; background: #f0b90b; }

/* 平仓弹窗：区块左右对齐 + 比例滑块/刻度（原 pct-* 未定义样式会导致刻度粘连） */
.close-sheet-stack .tpsl-pos-summary {
  padding: 10px 12px;
}
.close-sheet-stack .tpsl-info-rows {
  padding: 10px 12px;
  margin: 0;
}
.close-sheet-stack .inp-block {
  padding-left: 0;
  padding-right: 0;
}
.close-sheet-stack .cs-tabs {
  padding-left: 0;
  padding-right: 0;
}
.close-sheet-stack .pct-wrap {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 2px 0 0;
  width: 100%;
}
.close-sheet-stack .pct-track-wrap {
  position: relative;
  padding: 6px 10px 20px;
  box-sizing: border-box;
}
.close-sheet-stack .pct-slider {
  display: block;
  width: 100%;
  margin: 0;
  height: 20px;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}
.close-sheet-stack .pct-slider:focus {
  outline: none;
}
.close-sheet-stack .pct-slider::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.12);
}
.close-sheet-stack .pct-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -5px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
}
.close-sheet-stack .pct-slider::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.12);
}
.close-sheet-stack .pct-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: none;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
}
.close-sheet-stack .pct-dots {
  display: flex;
  justify-content: space-between;
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 8px;
  pointer-events: none;
}
.close-sheet-stack .pct-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
}
.close-sheet-stack .pct-dot.fill {
  background: #f0b90b;
}
.close-sheet-stack .pct-ticks {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 4px;
  padding: 0 10px;
  font-size: 11px;
  color: #6a7182;
  line-height: 1.3;
}
.close-sheet-stack .pct-ticks span:nth-child(1) {
  text-align: left;
}
.close-sheet-stack .pct-ticks span:nth-child(2),
.close-sheet-stack .pct-ticks span:nth-child(3),
.close-sheet-stack .pct-ticks span:nth-child(4) {
  text-align: center;
}
.close-sheet-stack .pct-ticks span:nth-child(5) {
  text-align: right;
}

/* TP/SL info rows */
.tpsl-info-rows {
  display: flex; flex-direction: column; gap: 4px;
  margin: 8px 0;
  padding: 8px 10px;
  background: rgba(255,255,255,0.04);
  border-radius: 6px;
}
.tpsl-info-row {
  display: flex; justify-content: space-between;
  font-size: 12px;
}
.tpsl-info-row span:first-child { color: #6a7182; }
.tpsl-info-row span:last-child { color: #c7cce4; font-weight: 500; }

/* Assets tab */
.assets-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 12px; padding: 16px;
}
.asset-card {
  padding: 14px 16px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 8px;
}
.asset-lbl { font-size: 12px; color: #6a7182; margin-bottom: 6px; }
.asset-val {
  font-size: 18px; font-weight: 700; color: #e8ecf4;
  font-variant-numeric: tabular-nums;
}
.asset-val.up { color: #02c076; }
.asset-val.dn { color: #f6465d; }
.asset-cur { font-size: 11px; color: #8b8b8e; font-weight: 500; margin-left: 4px; }
.asset-card--actions {
  display: flex; align-items: center; justify-content: center;
}
.asset-btn {
  padding: 8px 18px; border-radius: 6px;
  background: #f0b90b; color: #1a1a1a;
  font-size: 13px; font-weight: 600;
  transition: opacity .12s;
}
.asset-btn:hover { opacity: 0.9; }

/* Order type */
.ot-row {
  display: flex; align-items: center;
  padding: 2px 10px 8px; gap: 2px;
  border-bottom: 1px solid #212121;
}
.ot-btn {
  padding: 5px 9px; font-size: 12px; font-weight: 500;
  border-radius: 4px; color: #6a7182; transition: all .12s;
}
.ot-btn.act { background: rgba(255,255,255,0.08); color: #e8ecf4; }
.ot-cond { display: flex; align-items: center; gap: 3px; }
.ot-cond-wrap { position: relative; }
.cond-drop {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0;
  background: #1e2028; border: 1px solid #2a2a2a; border-radius: 4px;
  overflow: hidden; z-index: 200;
}
.cond-drop-item {
  padding: 8px 10px; font-size: 12px; color: #8a91a0; cursor: pointer;
  transition: background .12s, color .12s;
}
.cond-drop-item:hover { background: rgba(255,255,255,0.06); color: #e8ecf4; }
.cond-drop-item.act { color: var(--color-primary); }
.ot-info {
  margin-left: auto; color: #6a7182;
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; transition: all .12s;
}
.ot-info:hover { color: #c7cce4; }

/* Panel body */
.tp-body { display: flex; flex-direction: column; padding-bottom: 10px; }
.avail-row {
  display: flex; align-items: center;
  font-size: 12px; padding: 8px 12px;
}
.avail-lbl { color: #6a7182; margin-right: 6px; flex-shrink: 0; }
.avail-val { color: #c7cce4; font-weight: 500; flex: 1; }
.avail-acts { display: flex; gap: 4px; }

.avail-act {
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  color: #6a7182; border-radius: 3px; border: 1px solid rgba(255,255,255,0.1);
  transition: all .12s;
}
.avail-act:hover { color: #c7cce4; border-color: rgba(255,255,255,0.25); }

/* Input blocks */
.inp-block { padding: 6px 12px 0; }
.inp-wrap {
  background: rgba(255,255,255,0.12);
  border-radius: 8px;
  padding: 8px 10px 6px;
  transition: background .15s;
}
.inp-wrap:focus-within { background: rgba(255,255,255,0.15); }
.inp-lbl { display: block; font-size: 12px; color: #8b8b8e; margin-bottom: 2px; }
.inp-lbl-row {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 2px;
}
.inp-lbl-row .inp-lbl { margin-bottom: 0; }
.inp-row { display: flex; align-items: center; gap: 4px; }
.inp {
  flex: 1; background: none; color: #fff;
  font-size: 18px; min-width: 0; padding: 0; font-weight: 700;
  border: none; outline: none;
  -webkit-appearance: none; appearance: none;
}
.inp::placeholder { color: #5a6278; font-size: 12px; font-weight: 400; }
.inp::-webkit-inner-spin-button, .inp::-webkit-outer-spin-button { display: none; }
.inp-quick {
  padding: 2px 8px; font-size: 12px; font-weight: 700; color: #f0b90b;
  border-radius: 4px; flex-shrink: 0; white-space: nowrap;
  transition: all .12s;
}
.inp-quick:hover { background: rgba(240,185,11,0.1); }
.inp-bbo {
  background: rgba(255,255,255,0.12); color: #fff; font-weight: 400;
  padding: 3px 8px; border-radius: 6px;
}
.inp-bbo:hover { background: rgba(255,255,255,0.18); }
.inp-unit {
  font-size: 12px; color: #8b8b8e; flex-shrink: 0;
}
.inp-market {
  height: 42px; display: flex; align-items: center;
  padding: 0 10px; background: rgba(255,255,255,0.12);
  border-radius: 8px; color: #6a7182; font-size: 13px;
}
.vol-unit-btn {
  display: flex; align-items: center; gap: 3px;
  font-size: 11px; color: #9298ab; transition: all .12s; flex-shrink: 0;
}
.vol-unit-btn:hover { color: #c7cce4; }

/* PCT buttons */
.pct-btns { display: flex; gap: 4px; padding: 10px 12px 4px; }
.pct-btn {
  flex: 1; font-size: 12px; padding: 4px 0; border-radius: 4px;
  border: 1px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.04);
  color: #8b8b8e; cursor: pointer; transition: all .15s;
}
.pct-btn:hover { background: rgba(255,255,255,0.1); color: #e8ecf4; }

.trade-err { font-size: 12px; color: #f6465d; padding: 0 12px 4px; }

/* Submit buttons (logged in) */
.submit-row {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 8px; padding: 4px 12px;
}
.sub-btn {
  height: 44px; border-radius: 6px; font-size: 14px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  transition: opacity .15s;
}
.sub-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.sub-buy { background: #02c076; color: #0b0e16; }
.sub-sell { background: #f6465d; color: #fff; }
.sub-buy:not(:disabled):hover, .sub-sell:not(:disabled):hover { opacity: 0.9; }
.spin-sm {
  display: inline-block; width: 16px; height: 16px;
  border: 2px solid rgba(255,255,255,0.3); border-top-color: currentColor;
  border-radius: 50%; animation: spin .7s linear infinite;
}

/* Auth buttons (not logged in) */
.auth-btns {
  display: flex; flex-direction: column; gap: 8px;
  padding: 4px 12px;
}
.auth-btn {
  width: 100%; height: 40px; border-radius: 9999px;
  font-size: 14px; font-weight: 500;
  display: flex; align-items: center; justify-content: center;
  text-decoration: none; transition: all .15s;
}
.auth-register {
  background: #fff; color: #000;
}
.auth-register:hover { background: rgba(255,255,255,0.9); }
.auth-login {
  background: rgba(255,255,255,0.12); color: #fff;
}
.auth-login:hover { background: rgba(255,255,255,0.18); }

/* Contract info */
.contract-info {
  border-top: 1px solid #212121;
  margin: 6px 12px 0; padding-top: 10px;
}
.ci-title { font-size: 12px; font-weight: 600; color: #9298ab; margin-bottom: 8px; }
.ci-row { display: flex; justify-content: space-between; font-size: 11px; color: #5a6278; margin-bottom: 6px; }
.ci-row span:last-child { color: #9298ab; text-align: right; }

/* ─── Bottom area ─── */
.bottom-area {
  grid-column: 1;
  grid-row: 3;
  background: #000;
  border-top: 1px solid #212121;
  display: flex;
  flex-direction: column;
  min-height: 260px;
  min-width: 0;
}
.tab-scroll-area {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
  min-width: 0;
}
.tab-scroll-area::-webkit-scrollbar { width: 3px; }
.tab-scroll-area::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
.btab-bar {
  display: flex; align-items: center;
  padding: 0 12px; height: 42px;
  border-bottom: 1px solid #212121;
}
.btab-sp { flex: 1; }
.btab {
  padding: 0 12px; height: 100%;
  font-size: 12.5px; color: #6a7182;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: all .15s; display: flex; align-items: center; gap: 4px;
  white-space: nowrap;
}
.btab:hover { color: #c7cce4; }
.btab.act { color: #f0b90b; border-bottom-color: #f0b90b; }
.badge {
  font-size: 10px; background: rgba(240,185,11,0.15); color: #f0b90b;
  border-radius: 10px; padding: 0 5px; min-width: 16px; text-align: center;
}
.only-cur {
  display: flex; align-items: center; gap: 5px;
  font-size: 12px; color: #6a7182; cursor: pointer; padding: 0 8px;
  white-space: nowrap;
}
.only-cur input { accent-color: #f0b90b; }
.close-all {
  padding: 5px 12px; font-size: 12px; font-weight: 500;
  background: transparent; color: #f0b90b;
  border: 1px solid rgba(240,185,11,0.45); border-radius: 4px;
  transition: all .12s; white-space: nowrap; margin-left: 6px;
}
.close-all:hover { background: rgba(240,185,11,0.1); }

/* Data tables */
.empty-tip {
  padding: 28px 16px; text-align: center; color: #6a7182; font-size: 13px;
  display: flex; align-items: center; justify-content: center; gap: 6px;
}
.trades-list { overflow-y: auto; flex: 1; }
.trades-list::-webkit-scrollbar { width: 3px; height: 3px; }
.trades-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
.trade-row {
  display: grid; grid-template-columns: 1fr 1fr 1fr;
  padding: 3px 10px; font-size: 12px; line-height: 1.6;
}
.trade-time { color: #6a7182; }
.tip-link { color: #f0b90b; text-decoration: none; }
.tip-link:hover { text-decoration: underline; }
/* 表格容器：列少时右侧留白，内容超宽才出滚动条 */
.data-tbl { overflow-x: auto; position: relative; }
.data-tbl::-webkit-scrollbar { width: 3px; height: 3px; }
.data-tbl::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }

/* 行通用：宽度自适应但不小于容器 */
.dt-head, .dt-row {
  display: grid;
  padding: 6px 10px;
  align-items: center; gap: 0; font-size: 12px;
  min-width: 100%; width: max-content;
  box-sizing: border-box;
}

/* 直接子 span 居中（> 只选直接子元素，不影响嵌套 span 如 entrust-type-tag） */
.dt-head > span, .dt-row > span {
  display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: center; padding: 0 5px;
}
.dt-head > span { color: #6a7182; }
/* 首列左对齐 */
.dt-head > span:first-child, .dt-row > span:first-child { text-align: left; }
/* 操作列：flex 布局，不截断 */
.dt-row > span.op-col {
  display: flex; overflow: visible; white-space: nowrap;
  text-align: left; padding: 0;
}

/* ── 统一基准列宽 ──
   普通列: 84px  宽列(大数字/盈亏): 92px  时间列: 104px
   细列(状态/类型): 70px  合约列: 84px  操作(仓位): 160px  操作(委托): 82px */

/* 仓位：合约|方向杠杆|开仓价|标记价|持仓量|保证金|保证金率|浮动盈亏|收益率|强平价|开仓时间|操作 */
.dt-head--pos, .dt-row--pos {
  grid-template-columns: 84px 84px 84px 84px 70px 92px 70px 92px 70px 84px 104px 160px;
}
/* 当前委托：合约|方向杠杆|类型|委托价|触发价|委托量|已成交|止盈价|止损价|时间|操作 */
.dt-head--ord, .dt-row--ord {
  grid-template-columns: 84px 84px 70px 84px 84px 70px 70px 84px 84px 104px 82px;
}
/* 固定操作列吸附右侧 */
.dt-head--pos > span:last-child,
.dt-head--ord > span:last-child {
  position: sticky; right: 0; z-index: 2; background: #000;
}
.dt-row--pos, .dt-row--ord { --row-bg: #000; }
.dt-row--pos:hover, .dt-row--ord:hover { --row-bg: #111; }
.dt-row--pos > span:last-child,
.dt-row--ord > span:last-child {
  position: sticky; right: 0; z-index: 1; background: var(--row-bg, #000);
}

/* 历史仓位：合约|方向杠杆|状态|委托量|开仓均价|收益|已成交量|平仓均价|收益率|开仓时间|平仓时间 */
.dt-head--hist, .dt-row--hist {
  grid-template-columns: 84px 84px 70px 70px 84px 92px 70px 84px 70px 104px 104px;
}
/* 历史委托：合约|方向杠杆|类型|状态|委托量|已成交量|开仓均价|委托价格|委托时间|成交时间 */
.dt-head--hist-ord, .dt-row--hist-ord {
  grid-template-columns: 84px 84px 70px 70px 70px 70px 84px 84px 104px 104px;
}
/* 成交明细：合约|方向|成交价|成交量|手续费|成交时间 */
.dt-head--trade, .dt-row--trade {
  grid-template-columns: 84px 84px 84px 70px 84px 104px;
}
/* 资金流水：类型|数量|币种|时间 */
.dt-head--funding, .dt-row--funding {
  grid-template-columns: 120px 100px 100px 104px;
}
.entrust-type-tag {
  display: inline-block; font-size: 10px; padding: 1px 4px; border-radius: 2px;
  background: rgba(255,255,255,0.08); color: #8a92a8; margin-left: 3px;
}
.load-more-row { display: flex; justify-content: center; padding: 10px 0; }
.load-more-btn {
  font-size: 12px; padding: 4px 20px;
  border: 1px solid #333; border-radius: 3px; color: #9298ab;
  transition: all .12s;
}
.load-more-btn:hover { border-color: #555; color: #d1d4dc; }
.dt-head { border-bottom: 1px solid #212121; }
.dt-row { color: #9298ab; border-bottom: 1px solid #1a1a1a; transition: background .12s; }
.dt-row:hover { background: #111; }
.fw6 { font-weight: 600; color: #e8ecf4 !important; }
.act-btn {
  font-size: 10px; padding: 2px 6px;
  border: 1px solid rgba(240,185,11,0.35); border-radius: 3px;
  color: #f0b90b; transition: all .12s; white-space: nowrap;
}
.act-btn:hover:not(:disabled) { background: rgba(240,185,11,0.08); }
.act-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.act-btn--sec {
  border-color: rgba(100,180,255,0.35); color: #64b4ff;
}
.act-btn--sec:hover:not(:disabled) { background: rgba(100,180,255,0.08); }
.act-btn--market {
  border-color: rgba(246,70,93,0.35); color: #f6465d;
}
.act-btn--market:hover:not(:disabled) { background: rgba(246,70,93,0.08); }
/* 操作列：按钮横向排列，不换行 */
.op-col {
  display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px; align-items: center;
}

/* ─── Colors ─── */
.up { color: #02c076 !important; }
.dn { color: #f6465d !important; }

/* ─── Transitions ─── */
.pop-enter-active, .pop-leave-active { transition: opacity .15s, transform .15s; }
.pop-enter-from, .pop-leave-to { opacity: 0; transform: translateY(-6px); }

/* ─── Transfer Modal ─── */
.modal-mask {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0,0,0,0.6);
  display: flex; align-items: center; justify-content: center;
}
.modal-box {
  width: 480px; background: #1e2026; border-radius: 12px;
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}
.modal-hd {
  display: flex; align-items: center; justify-content: space-between;
  padding: 20px 24px 16px; border-bottom: 1px solid #2a2d35;
}
.modal-title { font-size: 16px; font-weight: 600; color: #e8ecf4; }
.modal-close {
  color: #6a7182; padding: 4px; border-radius: 4px; transition: color .15s;
}
.modal-close:hover { color: #c7cce4; }
.modal-body { padding: 20px 24px; display: flex; flex-direction: column; gap: 18px; }
.modal-ft {
  display: flex; gap: 12px; padding: 16px 24px 24px;
}
.modal-cancel {
  flex: 1; height: 40px; border-radius: 6px;
  background: #2a2d35; color: #c7cce4; font-size: 14px;
}
.modal-cancel:hover { background: #363a45; }
.modal-confirm {
  flex: 1; height: 40px; border-radius: 6px;
  background: #f0b90b; color: #111; font-weight: 600; font-size: 14px;
}
.modal-confirm:hover:not(:disabled) { background: #f5c842; }
.modal-confirm:disabled { opacity: 0.5; cursor: not-allowed; }
.tf-label { font-size: 12px; color: #6a7182; margin-bottom: 8px; }
.tf-row { display: flex; flex-direction: column; gap: 6px; }
.tf-inp {
  background: #2a2d35; border: 1px solid #3a3e4a; border-radius: 6px;
  padding: 10px 12px; color: #e8ecf4; font-size: 14px; width: 100%;
}
.tf-inp:focus { outline: none; border-color: #f0b90b; }
.tf-inp--profit:focus { border-color: #02c076; }
.tf-inp--loss:focus   { border-color: #f6465d; }
.tf-err { font-size: 12px; color: #f6465d; margin-top: 4px; }
/* TP/SL modal 内部元素 */
.tpsl-pos-summary {
  display: flex; align-items: center; padding: 10px 14px;
  background: #1e2026; border-radius: 6px; font-size: 13px;
}
.tpsl-section-title {
  font-size: 11px; color: #6a7182; font-weight: 500;
  margin: 14px 0 8px; text-transform: uppercase; letter-spacing: .5px;
}
.tpsl-current-list { display: flex; flex-direction: column; gap: 6px; }
.tpsl-current-item {
  background: #1e2026; border-radius: 6px; padding: 10px 12px;
}
.tpsl-current-row {
  display: flex; align-items: center; gap: 8px; font-size: 12px;
}
.tpsl-badge {
  padding: 2px 7px; border-radius: 3px; font-size: 11px; font-weight: 600;
  flex-shrink: 0;
}
.tpsl-badge--profit { background: rgba(2,192,118,0.15); color: #02c076; }
.tpsl-badge--loss   { background: rgba(246,70,93,0.15);  color: #f6465d; }
.tpsl-current-detail { flex: 1; color: #c7cce4; }
.tpsl-current-detail strong { color: #e8ecf4; }
.tpsl-current-vol { color: #6a7182; font-size: 11px; flex-shrink: 0; }
.tf-select {
  display: flex; align-items: center; justify-content: space-between;
  background: #2a2d35; border-radius: 8px; padding: 12px 14px;
  color: #e8ecf4; font-size: 14px; cursor: pointer;
}
.tf-direction { display: flex; align-items: flex-end; gap: 10px; }
.tf-dir-col { flex: 1; }
.tf-dir-lbl { font-size: 12px; color: #6a7182; margin-bottom: 8px; }
.tf-swap {
  flex-shrink: 0; width: 36px; height: 44px; margin-bottom: 0;
  background: #2a2d35; border-radius: 8px; color: #c7cce4;
  display: flex; align-items: center; justify-content: center;
  transition: background .15s;
}
.tf-swap:hover { background: #363a45; }
.tf-amount-wrap {
  display: flex; align-items: center;
  background: #2a2d35; border-radius: 8px; padding: 0 14px;
  height: 46px;
}
.tf-amount-inp {
  flex: 1; background: none; color: #e8ecf4; font-size: 14px;
  outline: none; border: none;
}
.tf-amount-inp::placeholder { color: #3a3d45; }
.tf-amount-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.tf-currency { font-size: 13px; color: #6a7182; }
.tf-sep { color: #3a3d45; }
.tf-all { font-size: 13px; color: #f0b90b; }
.tf-all:hover { opacity: 0.8; }
.tf-select--static { cursor: default; }
.tf-select--btn { position: relative; cursor: pointer; user-select: none; }
.tf-select--btn:hover { background: #363a45; }
.tf-dropdown {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 100;
  background: #2a2d35; border: 1px solid #363a45; border-radius: 8px;
  overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
.tf-dropdown-item {
  padding: 10px 14px; font-size: 14px; color: #c7cce4; cursor: pointer;
  transition: background .12s;
}
.tf-dropdown-item:hover { background: #363a45; }
.tf-dropdown-item.active { color: #f0b90b; }
.tf-avail { font-size: 12px; color: #6a7182; margin-top: 8px; }
.tf-avail span { color: #c7cce4; }
.tf-avail-loading { color: #6a7182; font-style: italic; }
.tf-error { font-size: 12px; color: #f6465d; margin-top: 6px; }
.tf-cancel {
  flex: 1; height: 44px; border-radius: 8px;
  background: #2a2d35; color: #c7cce4; font-size: 14px; font-weight: 500;
  transition: background .15s;
}
.tf-cancel:hover { background: #363a45; }
.tf-confirm {
  flex: 1; height: 44px; border-radius: 8px;
  background: #f0b90b; color: #000; font-size: 14px; font-weight: 600;
  transition: opacity .15s;
}
.tf-confirm:hover { opacity: 0.9; }
.tf-cancel:disabled, .tf-confirm:disabled { opacity: 0.5; cursor: not-allowed; }
.tf-amount-inp:disabled { opacity: 0.6; }
.modal-enter-active, .modal-leave-active { transition: opacity .2s; }
.modal-enter-from, .modal-leave-to { opacity: 0; }
.modal-enter-active .modal-box, .modal-leave-active .modal-box { transition: transform .2s; }
.modal-enter-from .modal-box, .modal-leave-to .modal-box { transform: scale(0.95); }

/* ─── Responsive (desktop) ─── */
@media (max-width: 1200px) {
  .book-col { width: 200px; }
}
@media (max-width: 960px) {
  .book-col { display: none; }
}
@media (max-width: 860px) {
  .trade-panel:not(.mobile-show) { display: none; }
}

/* ─── Mobile H5 (≤ 768px) ─── */
@media (max-width: 768px) {
  /* Page: single-column flex */
  .ft-page {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    grid-template-columns: unset;
    grid-template-rows: unset;
  }
  /* page-body keeps display:contents so children are ft-page flex items */

  /* Ticker bar: compact */
  .ticker-bar {
    grid-column: unset; grid-row: unset;
    height: auto; min-height: 54px;
    padding: 8px 10px;
    flex-shrink: 0;
    position: relative;
  }
  .ticker-stats { display: none; }
  .ticker-price-wrap { border-right: none; padding: 0 12px; }
  .ticker-price { font-size: 20px; }
  .ticker-usd { display: none; }
  .pair-name { font-size: 16px; }
  .coin-icon { width: 26px; height: 26px; }
  .pair-drop { width: calc(100vw - 20px); }

  /* Mobile tab bar */
  .mobile-trade-tabs {
    display: flex;
    height: 40px;
    flex-shrink: 0;
    background: #000;
    border-bottom: 1px solid #212121;
  }
  .mtt-btn {
    flex: 1;
    font-size: 14px;
    font-weight: 500;
    color: #6a7182;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: all .15s;
  }
  .mtt-btn.act { color: #f0b90b; border-bottom-color: #f0b90b; }

  /* Chart area */
  .chart-area {
    grid-column: unset; grid-row: unset;
    height: 280px;
    flex-shrink: 0;
  }
  .chart-area.mobile-hidden { display: none; }

  /* Drawing sidebar: hidden on mobile */
  .draw-side { display: none; }

  /* Sub-nav: horizontal scroll */
  .sub-nav { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .sub-nav::-webkit-scrollbar { display: none; }

  /* TF bar: horizontal scroll */
  .tf-bar { overflow-x: auto; -webkit-overflow-scrolling: touch; padding-right: 8px; }
  .tf-bar::-webkit-scrollbar { display: none; }
  .tf-spacer { min-width: 8px; flex: unset; }

  /* OHLCV overlay: smaller */
  .ohlcv-overlay { font-size: 10px; gap: 4px; padding: 2px 4px; }

  /* Kline tooltip: smaller */
  .kline-tooltip { font-size: 10px; padding: 4px 8px; min-width: 120px; }

  /* Trade panel: hidden by default, full-width when active */
  .trade-panel {
    grid-column: unset; grid-row: unset;
    display: none;
    flex-shrink: 0;
    width: 100%;
    max-height: none;
    overflow-y: auto;
  }
  .trade-panel.mobile-show { display: flex; }

  /* Bottom area */
  .bottom-area {
    grid-column: unset; grid-row: unset;
    min-height: 220px;
    flex-shrink: 0;
    flex: 1;
  }

  /* Bottom tab bar: horizontal scroll */
  .btab-bar { overflow-x: auto; -webkit-overflow-scrolling: touch; padding: 0 6px; }
  .btab-bar::-webkit-scrollbar { display: none; }
  .btab { padding: 0 8px; font-size: 12px; }

  /* Data tables: touch scroll */
  .data-tbl { -webkit-overflow-scrolling: touch; }

  /* Transfer modal: narrower */
  .modal-box { width: calc(100vw - 24px); }
  .modal-body { padding: 16px; gap: 14px; }
  .modal-ft { padding: 12px 16px 20px; }


  /* Detail modal: full width on mobile */
  .detail-modal { width: calc(100vw - 24px); max-height: 92vh; }
}

/* ─── Detail Modal ─── */
.detail-modal {
  width: 500px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
}
.detail-modal-body {
  overflow-y: auto;
  flex: 1;
  padding-bottom: 16px;
}
.dt-row--clickable { cursor: pointer; }
.dt-row--clickable:hover { background: #111 !important; }

.detail-header {
  display: flex; align-items: center; gap: 8px;
  padding: 14px 20px 14px;
  border-bottom: 1px solid #2a2d35;
}
.detail-dir-chip {
  padding: 2px 8px; border-radius: 4px;
  font-size: 12px; font-weight: 700;
  flex-shrink: 0;
}
.chip--up { background: rgba(2,192,118,0.15); color: #02c076; }
.chip--dn { background: rgba(246,70,93,0.15);  color: #f6465d; }
.detail-symbol {
  font-size: 15px; font-weight: 700; color: #e8ecf4; flex: 1;
}
.detail-tag {
  padding: 2px 7px; border-radius: 4px;
  font-size: 11px; font-weight: 500;
  background: rgba(100,180,255,0.12); color: #64b4ff;
  flex-shrink: 0;
}
.detail-tag--lev {
  background: rgba(255,255,255,0.08); color: #9298ab;
}

.detail-status-block {
  display: flex; flex-direction: column; align-items: center;
  padding: 28px 20px 20px;
}
.detail-status-icon {
  width: 52px; height: 52px; border-radius: 14px;
  background: rgba(100,180,255,0.1);
  display: flex; align-items: center; justify-content: center;
  color: #64b4ff; margin-bottom: 12px;
}
.detail-status-label {
  font-size: 16px; font-weight: 600;
}
.detail-close-type {
  font-size: 12px; color: #6a7182; margin-top: 4px;
}

.detail-section {
  background: #161a23;
  border-top: 1px solid #2a2d35;
  border-bottom: 1px solid #2a2d35;
}
.detail-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 11px 20px;
  border-bottom: 1px solid rgba(255,255,255,0.04);
  font-size: 13px;
}
.detail-row:last-child { border-bottom: none; }
.detail-lbl { color: #6a7182; }
.detail-val { color: #e8ecf4; font-weight: 500; text-align: right; }
.detail-section-gap { height: 8px; background: #0d0f14; }

/* ── 币种资料面板 ─────────────────────────────────── */
.coin-info-panel {
  position: absolute; inset: 0; overflow-y: auto; padding: 16px;
  background: #0d1117; z-index: 2;
}
.ci-loading { display: flex; align-items: center; justify-content: center; height: 100px; }
.cip-header {
  display: flex; align-items: center; gap: 10px;
  padding-bottom: 14px; border-bottom: 1px solid #1e2330; margin-bottom: 14px;
}
.cip-icon { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; }
.cip-names { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.cip-name-cn { font-size: 15px; font-weight: 600; color: #e8ecf4; }
.cip-name-en { font-size: 12px; color: #6a7182; }
.cip-rank {
  font-size: 11px; font-weight: 600; color: #f0b90b;
  background: rgba(240,185,11,0.12); border-radius: 4px; padding: 2px 7px;
}
.cip-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: #1e2330; border-radius: 6px; overflow: hidden;
}
.cip-item {
  display: flex; flex-direction: column; gap: 4px;
  background: #131720; padding: 10px 12px;
}
.cip-item--full { grid-column: 1 / -1; }
.cip-lbl { font-size: 11px; color: #6a7182; }
.cip-val { font-size: 13px; color: #e8ecf4; font-weight: 500; word-break: break-all; }
.cip-link { font-size: 12px; color: #4a8fff; text-decoration: none; word-break: break-all; }
.cip-link:hover { text-decoration: underline; }
.cip-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; height: 100px; color: #6a7182; font-size: 13px;
}
.cip-retry {
  background: transparent; border: 1px solid #2a2d35; color: #9ca3b0;
  border-radius: 4px; padding: 4px 14px; cursor: pointer; font-size: 12px;
}
.cip-retry:hover { border-color: #555; color: #d1d4dc; }

/* ── 历史资金费率面板 ───────────────────────────────── */
.fr-panel {
  position: absolute; inset: 0; display: flex; flex-direction: column; overflow-y: auto;
  background: #0d1117; z-index: 2;
}
.fr-info-bar {
  display: flex; gap: 0; border-bottom: 1px solid #1e2330; flex-shrink: 0;
  background: #0d1117;
}
.fr-info-item {
  flex: 1; display: flex; flex-direction: column; gap: 3px;
  padding: 10px 14px; border-right: 1px solid #1e2330;
}
.fr-info-item:last-child { border-right: none; }
.fr-info-lbl { font-size: 11px; color: #6a7182; }
.fr-info-val { font-size: 13px; font-weight: 600; color: #e8ecf4; }
.fr-body {
  flex: 1; display: flex; min-height: 0; overflow: hidden;
}
.fr-left {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  border-right: 1px solid #1e2330;
}
.fr-chart-wrap {
  flex: 1; min-height: 0; background: #0d1117; overflow: hidden;
}
.fr-chart-svg { width: 100%; height: 100%; display: block; }
.fr-chart-empty {
  display: flex; align-items: center; justify-content: center;
  height: 100%; color: #6a7182; font-size: 13px;
}
.fr-list { width: 240px; flex-shrink: 0; overflow: hidden; }
.fr-head {
  display: grid; grid-template-columns: 150px 1fr;
  padding: 7px 10px; font-size: 11px; color: #6a7182;
  border-bottom: 1px solid #1e2330; position: sticky; top: 0; background: #0d1117;
}
.fr-row {
  display: grid; grid-template-columns: 150px 1fr;
  padding: 8px 10px; font-size: 12px; color: #c9d1dc;
  border-bottom: 1px solid rgba(255,255,255,0.04);
  white-space: nowrap;
}
.fr-row:last-child { border-bottom: none; }
.fr-empty { padding: 20px 12px; color: #6a7182; font-size: 13px; text-align: center; }
</style>
