/* global React */
const { useState, useEffect, useMemo } = React;

// ── Runtime i18n ───────────────────────────────────────────────────────
// Detect locale from URL prefix (/no, /ru, /zh, default en) and look up the
// translation table. `T.someKey` returns the translated string or falls back
// to the English value, or the key itself if neither exists. The language
// doesn't change without a page reload, so we don't need a context/hook.
const _HZ_LANG = (function () {
  const p = location.pathname;
  if (p.startsWith('/no')) return 'no';
  if (p.startsWith('/ru')) return 'ru';
  if (p.startsWith('/zh')) return 'zh';
  return 'en';
})();
const _HZ_T = {
  en: {
    sendOffer: 'Send Trade Offer',
    balanceLbl: 'Balance',
    tradeUrl: 'Trade URL',
    setTradeUrl: 'Set Steam Trade URL',
    edit: 'Edit',
    player: 'Player',
    signIn: 'Sign in',
    youGive: 'You give',
    botGivesYou: 'Bot gives you',
    botAsks: 'Bot asks',
    yourItems: 'Your items',
    yourBalance: 'Your balance',
    balanceAfter: 'Balance after trade',
    feeNote: '4–7% service fee on all trades',
    toggles: 'Toggles',
    statTrakOnly: 'StatTrak™ only',
    souvenirOnly: 'Souvenir only',
    tradeLock: 'Trade-lock',
    lessThan: 'Less than',
    noLock: 'No lock',
    dayOne: 'day',
    dayMany: 'days',
    category: 'Category',
    exterior: 'Exterior',
    catAny: 'Any',
    catKnife: 'Knife',
    catGloves: 'Gloves',
    catPistol: 'Pistol',
    catSMG: 'SMG',
    catRifle: 'Assault Rifle',
    catSniper: 'Sniper Rifle',
    catShotgun: 'Shotgun',
    catMG: 'Machine Gun',
    catAgent: 'Agent',
    catContainer: 'Container',
    catMusicKit: 'Music Kit',
    catPatch: 'Patch',
    catSticker: 'Sticker',
    wearAny: 'Any',
    wearFN: 'Factory New',
    wearMW: 'Minimal Wear',
    wearFT: 'Field-Tested',
    wearWW: 'Well-Worn',
    wearBS: 'Battle-Scarred',
    showRecent: 'Show recent trades',
    recentTrades: 'Recent trades',
    youGot: 'You got',
    youGave: 'You gave',
    fee: 'fee',
    inventoryYour: 'Your inventory',
    inventoryBot: 'Bot inventory',
    refresh: 'Refresh',
    tradeOfferSent: 'Trade offer sent',
    close: 'Close',
    inspectDetails: 'Inspect details',
    viewOnSteam: 'View on Steam',
    openInSteam: 'Open in Steam ↗',
    offerId: 'Offer ID',
    paste: 'Paste',
    inspecting3D: '3D inspect',
    whereTradeUrl: 'Where do I find my Trade URL?',
    searchSkins: 'Search skins...',
    priceHigh: 'Price: High → Low',
    priceLow: 'Price: Low → High',
    toggleSort: 'Toggle sort direction',
    rangeAll: 'All',
    rangeUnder: 'Under',
    signInToLoad: 'Sign in to load your inventory',
    botOffline: 'Bot inventory · offline',
    tradeTab: 'Trade',
    tradePanelsAria: 'Trade panels',
    noItemsShow: 'No items to show — sign in or wait for your inventory to load.',
    botEmpty: 'Bot inventory is empty.',
    retry: 'Retry',
    selectFromBot: 'Select items from the bot inventory',
    selectFromYours: 'Select items from your inventory (optional if you have balance)',
    noTradesYet: 'No trades yet.',
    statusCompleted: 'Completed',
    closeRecentAria: 'Close recent trades',
    lockSliderAria: 'Less than N days of trade-lock',
    balanceCredited: 'balance credited',
    balanceUsed: 'balance used',
    errSomethingWrong: 'Something went wrong',
    cancel: 'Cancel',
    confirm: 'Confirm',
    moreValueBadge: 'More value',
    highDemandTip: 'High demand — paid at +{pct}% over market',
    tradeUrlClickChange: 'Click to change your Steam Trade URL',
    tradeUrlClickSet: 'Click to set your Steam Trade URL',
    tradeUrlPasteDesc: 'Paste your Steam Trade URL so the bot can send offers to you. Find it in Steam → Inventory → Trade Offers → Who can send me Trade Offers.',
    openSteamProfile: 'Open Steam profile',
    signInWithSteam: 'Sign in with Steam',
    contactLede: 'Need help with a trade, dispute or general question? Our mods are on Discord 24/7.',
    contactCommunity: 'Hostadz Community',
    contactMembers: '2,418 members · 412 online',
    contactCopied: '✓ Copied to clipboard',
    contactCopyInvite: 'Copy invite link',
    ftrFAQ: 'FAQ',
    ftrGuides: 'Guides',
    ftrPrivacy: 'Privacy',
    ftrTerms: 'Terms',
    ftrContact: 'Contact',
    ftrMeta: '© 2025 · Not affiliated with Valve / Steam',
    itemsCount: 'items',
    pricesAge: 'prices',
    'nav.home': 'Home',
    'nav.trade': 'Trade',
    'nav.calculator': 'Calculator',
    'nav.steamid': 'SteamID',
    'nav.guides': 'Guides',
    'nav.faq': 'FAQ',
  },
  no: {
    sendOffer: 'Send byttetilbud',
    balanceLbl: 'Saldo',
    tradeUrl: 'Bytte-URL',
    setTradeUrl: 'Sett Steam Bytte-URL',
    edit: 'Endre',
    player: 'Spiller',
    signIn: 'Logg inn',
    youGive: 'Du gir',
    botGivesYou: 'Bot gir deg',
    botAsks: 'Bot ber om',
    yourItems: 'Dine varer',
    yourBalance: 'Din saldo',
    balanceAfter: 'Saldo etter bytte',
    feeNote: '4–7 % gebyr på alle bytter',
    toggles: 'Brytere',
    statTrakOnly: 'Kun StatTrak™',
    souvenirOnly: 'Kun Souvenir',
    tradeLock: 'Byttelås',
    lessThan: 'Mindre enn',
    noLock: 'Ingen lås',
    dayOne: 'dag',
    dayMany: 'dager',
    category: 'Kategori',
    exterior: 'Slitasje',
    catAny: 'Alle',
    catKnife: 'Kniv',
    catGloves: 'Hansker',
    catPistol: 'Pistol',
    catSMG: 'Maskinpistol',
    catRifle: 'Rifle',
    catSniper: 'Snikskytter',
    catShotgun: 'Hagle',
    catMG: 'Maskingevær',
    catAgent: 'Agent',
    catContainer: 'Kasse',
    catMusicKit: 'Musikkpakke',
    catPatch: 'Lapp',
    catSticker: 'Klistremerke',
    wearAny: 'Alle',
    wearFN: 'Fabrikkny',
    wearMW: 'Minimal slitasje',
    wearFT: 'Feltprøvd',
    wearWW: 'Godt brukt',
    wearBS: 'Kampskadet',
    showRecent: 'Vis nylige bytter',
    recentTrades: 'Nylige bytter',
    youGot: 'Du fikk',
    youGave: 'Du gav',
    fee: 'gebyr',
    inventoryYour: 'Ditt lager',
    inventoryBot: 'Bot-lager',
    refresh: 'Oppdater',
    tradeOfferSent: 'Byttetilbud sendt',
    close: 'Lukk',
    inspectDetails: 'Inspiser detaljer',
    viewOnSteam: 'Vis på Steam',
    openInSteam: 'Åpne i Steam ↗',
    offerId: 'Tilbuds-ID',
    paste: 'Lim inn',
    inspecting3D: '3D-inspeksjon',
    whereTradeUrl: 'Hvor finner jeg Bytte-URL-en min?',
    searchSkins: 'Søk skins...',
    priceHigh: 'Pris: Høy → Lav',
    priceLow: 'Pris: Lav → Høy',
    toggleSort: 'Bytt sorteringsretning',
    rangeAll: 'Alle',
    rangeUnder: 'Under',
    signInToLoad: 'Logg inn for å laste lageret ditt',
    botOffline: 'Bot-lager · offline',
    tradeTab: 'Bytt',
    tradePanelsAria: 'Bytte-paneler',
    noItemsShow: 'Ingen varer å vise — logg inn eller vent på at lageret lastes.',
    botEmpty: 'Bot-lageret er tomt.',
    retry: 'Prøv igjen',
    selectFromBot: 'Velg varer fra bot-lageret',
    selectFromYours: 'Velg varer fra ditt lager (valgfritt hvis du har balanse)',
    noTradesYet: 'Ingen bytter ennå.',
    statusCompleted: 'Fullført',
    closeRecentAria: 'Lukk nylige bytter',
    lockSliderAria: 'Mindre enn N dager med byttelås',
    balanceCredited: 'kreditert balanse',
    balanceUsed: 'balanse brukt',
    errSomethingWrong: 'Noe gikk galt',
    cancel: 'Avbryt',
    confirm: 'Bekreft',
    moreValueBadge: 'Mer verdi',
    highDemandTip: 'Høy etterspørsel — betalt +{pct} % over markedet',
    tradeUrlClickChange: 'Klikk for å endre Steam Trade URL',
    tradeUrlClickSet: 'Klikk for å sette Steam Trade URL',
    tradeUrlPasteDesc: 'Lim inn Steam Trade URL slik at boten kan sende tilbud til deg. Finn den i Steam → Inventory → Trade Offers → Who can send me Trade Offers.',
    openSteamProfile: 'Åpne Steam-profil',
    signInWithSteam: 'Logg inn med Steam',
    contactLede: 'Trenger du hjelp med et bytte, en tvist eller et generelt spørsmål? Modene våre er på Discord 24/7.',
    contactCommunity: 'Hostadz-fellesskapet',
    contactMembers: '2 418 medlemmer · 412 pålogget',
    contactCopied: '✓ Kopiert til utklippstavlen',
    contactCopyInvite: 'Kopier invitasjonslenke',
    ftrFAQ: 'FAQ',
    ftrGuides: 'Guider',
    ftrPrivacy: 'Personvern',
    ftrTerms: 'Vilkår',
    ftrContact: 'Kontakt',
    ftrMeta: '© 2025 · Ikke tilknyttet Valve / Steam',
    itemsCount: 'varer',
    pricesAge: 'priser',
    'nav.home': 'Hjem',
    'nav.trade': 'Bytt',
    'nav.calculator': 'Kalkulator',
    'nav.steamid': 'SteamID',
    'nav.guides': 'Guider',
    'nav.faq': 'FAQ',
  },
  ru: {
    sendOffer: 'Отправить обмен',
    balanceLbl: 'Баланс',
    tradeUrl: 'Trade URL',
    setTradeUrl: 'Установить Steam Trade URL',
    edit: 'Изменить',
    player: 'Игрок',
    signIn: 'Войти',
    youGive: 'Вы отдаёте',
    botGivesYou: 'Бот даёт вам',
    botAsks: 'Бот просит',
    yourItems: 'Ваши предметы',
    yourBalance: 'Ваш баланс',
    balanceAfter: 'Баланс после обмена',
    feeNote: 'Комиссия 4–7 % на все обмены',
    toggles: 'Переключатели',
    statTrakOnly: 'Только StatTrak™',
    souvenirOnly: 'Только Souvenir',
    tradeLock: 'Блокировка',
    lessThan: 'Меньше',
    noLock: 'Без блокировки',
    dayOne: 'день',
    dayMany: 'дн.',
    category: 'Категория',
    exterior: 'Качество',
    catAny: 'Любая',
    catKnife: 'Нож',
    catGloves: 'Перчатки',
    catPistol: 'Пистолет',
    catSMG: 'ПП',
    catRifle: 'Винтовка',
    catSniper: 'Снайперка',
    catShotgun: 'Дробовик',
    catMG: 'Пулемёт',
    catAgent: 'Агент',
    catContainer: 'Кейс',
    catMusicKit: 'Музнабор',
    catPatch: 'Нашивка',
    catSticker: 'Наклейка',
    wearAny: 'Любое',
    wearFN: 'Прямо с завода',
    wearMW: 'Немного поношенное',
    wearFT: 'После полевых испытаний',
    wearWW: 'Поношенное',
    wearBS: 'Закалённое в боях',
    showRecent: 'Показать недавние обмены',
    recentTrades: 'Недавние обмены',
    youGot: 'Вы получили',
    youGave: 'Вы отдали',
    fee: 'комиссия',
    inventoryYour: 'Ваш инвентарь',
    inventoryBot: 'Инвентарь бота',
    refresh: 'Обновить',
    tradeOfferSent: 'Предложение отправлено',
    close: 'Закрыть',
    inspectDetails: 'Подробности',
    viewOnSteam: 'Открыть в Steam',
    openInSteam: 'Открыть в Steam ↗',
    offerId: 'ID предложения',
    paste: 'Вставить',
    inspecting3D: '3D-просмотр',
    whereTradeUrl: 'Где найти мой Trade URL?',
    searchSkins: 'Поиск скинов...',
    priceHigh: 'Цена: высокая → низкая',
    priceLow: 'Цена: низкая → высокая',
    toggleSort: 'Сменить направление сортировки',
    rangeAll: 'Все',
    rangeUnder: 'Меньше',
    signInToLoad: 'Войдите, чтобы загрузить инвентарь',
    botOffline: 'Инвентарь бота · оффлайн',
    tradeTab: 'Обмен',
    tradePanelsAria: 'Панели обмена',
    noItemsShow: 'Нет предметов — войдите или дождитесь загрузки инвентаря.',
    botEmpty: 'Инвентарь бота пуст.',
    retry: 'Повторить',
    selectFromBot: 'Выберите предметы из инвентаря бота',
    selectFromYours: 'Выберите предметы из своего инвентаря (необязательно, если есть баланс)',
    noTradesYet: 'Пока нет обменов.',
    statusCompleted: 'Завершён',
    closeRecentAria: 'Закрыть недавние обмены',
    lockSliderAria: 'Менее N дней блокировки обмена',
    balanceCredited: 'зачислено на баланс',
    balanceUsed: 'использовано с баланса',
    errSomethingWrong: 'Что-то пошло не так',
    cancel: 'Отмена',
    confirm: 'Подтвердить',
    moreValueBadge: 'Больше ценности',
    highDemandTip: 'Высокий спрос — оплата +{pct}% к рынку',
    tradeUrlClickChange: 'Нажмите, чтобы изменить Steam Trade URL',
    tradeUrlClickSet: 'Нажмите, чтобы задать Steam Trade URL',
    tradeUrlPasteDesc: 'Вставьте свой Steam Trade URL, чтобы бот мог отправлять вам предложения. Найдите его в Steam → Inventory → Trade Offers → Who can send me Trade Offers.',
    openSteamProfile: 'Открыть профиль Steam',
    signInWithSteam: 'Войти через Steam',
    contactLede: 'Нужна помощь с обменом, спором или общим вопросом? Наши модераторы в Discord 24/7.',
    contactCommunity: 'Сообщество Hostadz',
    contactMembers: '2 418 участников · 412 онлайн',
    contactCopied: '✓ Скопировано в буфер',
    contactCopyInvite: 'Скопировать ссылку-приглашение',
    ftrFAQ: 'FAQ',
    ftrGuides: 'Гайды',
    ftrPrivacy: 'Конфиденциальность',
    ftrTerms: 'Условия',
    ftrContact: 'Контакты',
    ftrMeta: '© 2025 · Не связан с Valve / Steam',
    itemsCount: 'предметов',
    pricesAge: 'цены',
    'nav.home': 'Главная',
    'nav.trade': 'Обмен',
    'nav.calculator': 'Калькулятор',
    'nav.steamid': 'SteamID',
    'nav.guides': 'Гайды',
    'nav.faq': 'FAQ',
  },
  zh: {
    sendOffer: '发送交易报价',
    balanceLbl: '余额',
    tradeUrl: '交易链接',
    setTradeUrl: '设置 Steam 交易链接',
    edit: '修改',
    player: '玩家',
    signIn: '登录',
    youGive: '您支付',
    botGivesYou: '机器人给您',
    botAsks: '机器人要价',
    yourItems: '您的物品',
    yourBalance: '您的余额',
    balanceAfter: '交易后余额',
    feeNote: '所有交易仅收取 4–7% 服务费',
    toggles: '开关',
    statTrakOnly: '仅 StatTrak™',
    souvenirOnly: '仅纪念品',
    tradeLock: '交易锁定',
    lessThan: '少于',
    noLock: '无锁定',
    dayOne: '天',
    dayMany: '天',
    category: '类别',
    exterior: '外观',
    catAny: '全部',
    catKnife: '匕首',
    catGloves: '手套',
    catPistol: '手枪',
    catSMG: '冲锋枪',
    catRifle: '突击步枪',
    catSniper: '狙击步枪',
    catShotgun: '霰弹枪',
    catMG: '机枪',
    catAgent: '探员',
    catContainer: '武器箱',
    catMusicKit: '音乐盒',
    catPatch: '布章',
    catSticker: '贴纸',
    wearAny: '全部',
    wearFN: '崭新出厂',
    wearMW: '略有磨损',
    wearFT: '久经沙场',
    wearWW: '破损不堪',
    wearBS: '战痕累累',
    showRecent: '显示近期交易',
    recentTrades: '近期交易',
    youGot: '您获得',
    youGave: '您支付',
    fee: '费用',
    inventoryYour: '您的库存',
    inventoryBot: '机器人库存',
    refresh: '刷新',
    tradeOfferSent: '交易报价已发送',
    close: '关闭',
    inspectDetails: '查看详情',
    viewOnSteam: '在 Steam 中查看',
    openInSteam: '在 Steam 中打开 ↗',
    offerId: '报价 ID',
    paste: '粘贴',
    inspecting3D: '3D 检视',
    whereTradeUrl: '在哪里找到我的交易链接？',
    searchSkins: '搜索饰品...',
    priceHigh: '价格：高 → 低',
    priceLow: '价格：低 → 高',
    toggleSort: '切换排序方向',
    rangeAll: '全部',
    rangeUnder: '低于',
    signInToLoad: '登录以加载您的库存',
    botOffline: '机器人库存 · 离线',
    tradeTab: '交易',
    tradePanelsAria: '交易面板',
    noItemsShow: '暂无物品 — 请登录或等待库存加载。',
    botEmpty: '机器人库存为空。',
    retry: '重试',
    selectFromBot: '从机器人库存中选择物品',
    selectFromYours: '从您的库存中选择物品（如有余额则可选）',
    noTradesYet: '暂无交易。',
    statusCompleted: '已完成',
    closeRecentAria: '关闭近期交易',
    lockSliderAria: '少于 N 天的交易锁定',
    balanceCredited: '余额已存入',
    balanceUsed: '已使用余额',
    errSomethingWrong: '出错了',
    cancel: '取消',
    confirm: '确认',
    moreValueBadge: '更多价值',
    highDemandTip: '高需求 — 按高于市场 +{pct}% 支付',
    tradeUrlClickChange: '点击修改 Steam 交易链接',
    tradeUrlClickSet: '点击设置 Steam 交易链接',
    tradeUrlPasteDesc: '粘贴您的 Steam 交易链接，机器人才能向您发送报价。在 Steam → Inventory → Trade Offers → Who can send me Trade Offers 中找到。',
    openSteamProfile: '打开 Steam 资料',
    signInWithSteam: '使用 Steam 登录',
    contactLede: '需要交易、争议或常规问题帮助？我们的版主全天候在 Discord。',
    contactCommunity: 'Hostadz 社区',
    contactMembers: '2,418 名成员 · 412 在线',
    contactCopied: '✓ 已复制到剪贴板',
    contactCopyInvite: '复制邀请链接',
    ftrFAQ: '常见问题',
    ftrGuides: '指南',
    ftrPrivacy: '隐私',
    ftrTerms: '条款',
    ftrContact: '联系',
    ftrMeta: '© 2025 · 与 Valve / Steam 无关联',
    itemsCount: '件',
    pricesAge: '价格',
    'nav.home': '首页',
    'nav.trade': '交易',
    'nav.calculator': '计算器',
    'nav.steamid': 'SteamID',
    'nav.guides': '指南',
    'nav.faq': '常见问题',
  },
};
// Translator: T('key') → translated string, falls back to EN, then to the key.
function T(key) {
  const m = _HZ_T[_HZ_LANG] || _HZ_T.en;
  if (m[key] !== undefined) return m[key];
  if (_HZ_T.en[key] !== undefined) return _HZ_T.en[key];
  return key;
}

// Currency formatter: accepts USD dollars (kit's native unit) and converts via
// the global currency selector. Falls back to USD if the helper hasn't loaded.
function formatPrice(dollars) {
  const cents = Math.round((Number(dollars) || 0) * 100);
  if (window.HZCurrency && typeof window.HZCurrency.format === 'function') {
    return window.HZCurrency.format(cents);
  }
  return '$' + (cents / 100).toFixed(2);
}
// React hook — re-render on currency or rate changes so prices stay in sync.
function useCurrencyTick() {
  const [, setTick] = useState(0);
  useEffect(() => {
    const bump = () => setTick(t => t + 1);
    document.addEventListener('hostadz:currency', bump);
    document.addEventListener('hostadz:rates', bump);
    return () => {
      document.removeEventListener('hostadz:currency', bump);
      document.removeEventListener('hostadz:rates', bump);
    };
  }, []);
}

// ─── shared lookups ───
const WEAR_LABEL = { fn: 'FN', mw: 'MW', ft: 'FT', ww: 'WW', bs: 'BS' };

const RARITY = {
  consumer:    { c: '#B0C3D9', name: 'Consumer'    },
  industrial:  { c: '#5E98D9', name: 'Industrial'  },
  milspec:     { c: '#4B69FF', name: 'Mil-Spec'    },
  restricted:  { c: '#8847FF', name: 'Restricted'  },
  classified:  { c: '#D32CE6', name: 'Classified'  },
  covert:      { c: '#EB4B4B', name: 'Covert'      },
  exceedingly: { c: '#E4AE39', name: '★ Rare'      },
  contraband:  { c: '#E4AE39', name: 'Contraband'  },
};

const PRICE_RANGES = [
  { k: 'all',  lo: 0,    hi: Infinity },
  { k: 'u5',   lo: 0,    hi: 5        },
  { k: 'r1',   lo: 5,    hi: 25       },
  { k: 'r2',   lo: 25,   hi: 100      },
  { k: 'r3',   lo: 100,  hi: Infinity },
];

// Convert a USD-dollar threshold to the user's active currency and format it
// as a clean, decimal-free chip label (e.g. $5 → "kr 53", "¥780", "€5").
// Filtering still uses the underlying dollar value so behaviour is consistent.
function unitPriceRounded(dollars) {
  if (!window.HZCurrency) return '$' + dollars;
  const code = window.HZCurrency.code || 'USD';
  const rate = window.HZCurrency.getRate ? window.HZCurrency.getRate(code) : 1;
  const converted = dollars * rate;
  // Whole-number rounding keeps chips compact. Currencies with small unit
  // values (JPY, NOK) round to nearest 10/100 for readability.
  let rounded;
  if (converted >= 1000)      rounded = Math.round(converted / 100) * 100;
  else if (converted >= 100)  rounded = Math.round(converted / 10) * 10;
  else                        rounded = Math.round(converted);
  try {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: code,
      maximumFractionDigits: 0,
      minimumFractionDigits: 0,
    }).format(rounded);
  } catch (_) {
    const meta = (window.HZCurrency.list || []).find(c => c.code === code);
    const sym = (meta && meta.symbol) || '$';
    const num = rounded.toLocaleString('en-US');
    return sym.length > 1 ? sym + ' ' + num : sym + num;
  }
}

// Format a price-range chip label. The numeric thresholds (5, 25, 100 USD)
// are converted to the user's currency, so the last chip is roughly worth
// $100 regardless of which currency they've picked.
function priceRangeLabel(r) {
  if (r.k === 'all') return T('rangeAll');
  if (r.lo === 0)        return T('rangeUnder') + ' ' + unitPriceRounded(r.hi);
  if (r.hi === Infinity) return unitPriceRounded(r.lo) + '+';
  return unitPriceRounded(r.lo) + '–' + unitPriceRounded(r.hi);
}

// ─── Header ──────────────────────────────────────────────
function Header({ active, onNav, balance, tradeUrl, onSetTradeUrl, user }) {
  // Nav labels go through T() so non-English visitors see translated nav.
  // Prefix all hrefs with the current language so /no/trade stays in NO etc.
  const langPrefix = _HZ_LANG === 'en' ? '' : '/' + _HZ_LANG;
  const links = [
    { key: 'Home',       label: T('nav.home') !== 'nav.home' ? T('nav.home') : 'Home',                 href: langPrefix || '/'             },
    { key: 'Trade',      label: T('nav.trade') !== 'nav.trade' ? T('nav.trade') : 'Trade',             href: langPrefix + '/trade'        },
    { key: 'Calculator', label: T('nav.calculator') !== 'nav.calculator' ? T('nav.calculator') : 'Calculator', href: langPrefix + '/calculator'   },
    { key: 'SteamID',    label: T('nav.steamid') !== 'nav.steamid' ? T('nav.steamid') : 'SteamID',     href: langPrefix + '/steamid'      },
    { key: 'Guides',     label: T('nav.guides') !== 'nav.guides' ? T('nav.guides') : 'Guides',         href: langPrefix + '/guides/'      },
    { key: 'FAQ',        label: T('nav.faq') !== 'nav.faq' ? T('nav.faq') : 'FAQ',                     href: langPrefix + '/faq'          },
  ];
  const tradeUrlShort = tradeUrl ? tradeUrl.replace(/^https?:\/\//, '').slice(0, 36) + '…' : T('setTradeUrl');
  // Real-user display: name + avatar from /auth/me. Falls back to a "Sign in" link if logged out.
  const displayName = user?.displayName || user?.username || '';
  const avatarUrl = user?.avatar || '';
  return (
    <header className="hdr">
      <div className="hdr-inner">
        <a className="hdr-logo" href="/">
          <span className="mark"><img src="/trade-kit/logo.png" alt=""/></span>
          <span><span className="word">Hostadz</span><span className="tld">.com</span></span>
        </a>
        <nav className="hdr-nav">
          {links.map(l => (
            <a key={l.key} href={l.href} className={l.key === active ? 'active' : ''} onClick={() => onNav?.(l.key)}>{l.label}</a>
          ))}
        </nav>
        <button
          className={'hdr-tradeurl' + (tradeUrl ? ' set' : '')}
          onClick={onSetTradeUrl}
          title={tradeUrl ? T('tradeUrlClickChange') : T('tradeUrlClickSet')}
        >
          <span className="lbl">{T('tradeUrl')}</span>
          <span className="v">{tradeUrlShort}</span>
          <span className="hdr-tradeurl-edit" aria-hidden="true">
            <svg viewBox="0 0 16 16" width="11" height="11" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
              <path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" />
            </svg>
            {T('edit')}
          </span>
        </button>
        <div className="hdr-bal">
          <span className="lbl">{T('balanceLbl')}</span>
          <span className="v">{formatPrice(balance)}</span>
        </div>
        {user ? (
          <a className="hdr-user" href={`https://steamcommunity.com/profiles/${user.steamId}`} target="_blank" rel="noopener noreferrer" title={T('openSteamProfile')}>
            {avatarUrl
              ? <img className="hdr-avatar" src={avatarUrl} alt="" width="28" height="28" style={{borderRadius:'50%',objectFit:'cover'}} />
              : <div className="hdr-avatar"></div>}
            <span className="name">{displayName || T('player')}</span>
          </a>
        ) : (
          <a className="hdr-user hdr-user--signin" href="/auth/steam" rel="nofollow" title={T('signInWithSteam')}>
            <div className="hdr-avatar"></div>
            <span className="name">{T('signIn')}</span>
          </a>
        )}
      </div>
    </header>
  );
}

// ─── Item card (rarity-colored name, sticker rail, float bar) ───

function ItemCard({ item, selected, onClick, side, hdBonusPct = 0 }) {
  const r = RARITY[item.rarity] || RARITY.covert;
  const rarityRgba = (alpha) => {
    const hex = r.c.replace('#', '');
    const n = parseInt(hex, 16);
    return `rgba(${(n >> 16) & 255},${(n >> 8) & 255},${n & 255},${alpha})`;
  };
  // Hide the sparkline + float bar on the user-inventory side per request.
  const showStats = side !== 'give';
  const floatPos = `${Math.min((item.float || 0) * 100, 99)}%`;
  const hasFloat = (item.float || 0) > 0;
  const stat = item.stat;
  // Prefer the rich sticker/charm data (with icon URLs) so we can render real images.
  const stickerObjs = Array.isArray(item.stickerData) ? item.stickerData : [];
  const charmObjs   = Array.isArray(item.charmData)   ? item.charmData   : [];
  const sticks = stickerObjs.length || item.stickers || 0;
  const charm = charmObjs.length > 0 || !!item.charm;
  const tradeLock = item.lockHours;

  // Inspect → open the bot's public Steam inventory page focused on this asset.
  // The Inspect button is bot-side only (hidden on the user's side), so we always
  // route to the canonical bot vanity URL.
  const handleInspect = (e) => {
    e.stopPropagation();
    window.open(
      `https://steamcommunity.com/id/Hostadxyz/inventory/#730_2_${item.assetid || ''}`,
      '_blank',
      'noopener,noreferrer'
    );
  };
  // In-app 3D viewer — bridges to /js/inspect-viewer.js via window.openInspect3D
  // (defined inline at the bottom of trade.html so the import map and module loader
  // are scoped to this page).
  const handle3D = (e) => {
    e.stopPropagation();
    if (typeof window.openInspect3D === 'function') {
      window.openInspect3D(item);
    } else if (item.inspectLink) {
      // Fallback: external CSFloat viewer if the bridge somehow didn't load
      window.open(`https://csfloat.com/?ref=hostadz&inspect=${encodeURIComponent(item.inspectLink)}`, '_blank', 'noopener,noreferrer');
    }
  };

  return (
    <div
      className={
        'icard r-' + item.rarity
        + (stat ? ' is-stat' : '')
        + (tradeLock != null ? ' is-locked' : '')
        + (selected ? ' icard--selected' : '')
      }
      style={{
        '--rarity': rarityRgba(0.55),
        '--rarity-strong': rarityRgba(0.9),
        '--rarity-solid': r.c,
      }}
      onClick={onClick}
    >
      {/* High-demand bonus — only on user-side cards. Tells the user this item
          is credited at the better .env-driven rate (HIGH_DEMAND_MARGIN).
          Pinned to the very top-left of the card frame (above the image margin). */}
      {side === 'give' && item.isHighDemand && (
        <div className="icard-hd-badge" title={T('highDemandTip').replace('{pct}', hdBonusPct)}>
          <span className="icard-hd-badge-lbl">{T('moreValueBadge')}</span>
          {hdBonusPct > 0 && <span className="icard-hd-badge-pct">+{hdBonusPct}%</span>}
        </div>
      )}
      {/* Hover actions are bot-side only — pinned to the very top-left of the
          card frame (above the image margin). User's items can't be inspected
          or 3D-previewed because we don't get inspect links / GC pattern data
          for items in another inventory we don't own. */}
      {side !== 'give' && (
        <div className="icard-actions">
          <button title={T('viewOnSteam')} onClick={handleInspect}>
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>
            </svg>
          </button>
          {/* Inspect modal stays clickable even on trade-locked items — the lock only
              blocks the trade itself, not viewing the skin's pattern / collection / stickers. */}
          <button title={T('inspectDetails')} onClick={handle3D}>
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.5" y2="16.5"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/>
            </svg>
          </button>
        </div>
      )}

      {/* Trade-lock badge — anchored top-right of the card frame. */}
      {tradeLock != null && (
        <span className="icard-lock-corner">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/>
          </svg>
          <span className="icard-lock-corner-time">
            {tradeLock < 24 ? `${tradeLock}h` : `${Math.floor(tradeLock/24)}d ${tradeLock%24}h`}
          </span>
        </span>
      )}

      <div className="icard-img">
        {/* Real Steam item image when available; falls back to short name label */}
        {item.iconUrl
          ? <img className="icard-img-photo" src={item.iconUrl} alt={item.name} loading="lazy" onError={(e)=>{e.target.style.display='none';}} />
          : <div className="icard-img-lbl">{item.shortName}</div>}

        {/* Sticker / charm rail — stacked vertically on the right edge of the
            image so the column scales with the art and never pushes the body
            down. */}
        {(sticks > 0 || charm) && (
          <div className="icard-rail">
            {stickerObjs.length > 0
              ? stickerObjs.slice(0, 5).map((s, i) => (
                  <span key={'s'+i} className="icard-rail-dot sticker" title={s.source ? `${s.name || 'Sticker'} — from ${s.source} capsule` : (s.name || 'Sticker')}>
                    {s.iconUrl
                      ? <img src={s.iconUrl} alt="" loading="lazy" onError={(e)=>{e.target.style.display='none';}} />
                      : (s.name || '').slice(0,2).toUpperCase()}
                  </span>
                ))
              : Array.from({ length: Math.min(sticks, 5) }).map((_, i) => (
                  <span key={'s'+i} className="icard-rail-dot sticker"></span>
                ))}
            {charmObjs.length > 0
              ? charmObjs.slice(0, 1).map((c, i) => (
                  <span key={'c'+i} className="icard-rail-dot charm" title={c.name || 'Charm'}>
                    {c.iconUrl
                      ? <img src={c.iconUrl} alt="" loading="lazy" onError={(e)=>{e.target.style.display='none';}} />
                      : (c.name || '').slice(0,2).toUpperCase()}
                  </span>
                ))
              : (charm && <span className="icard-rail-dot charm"></span>)}
          </div>
        )}
      </div>

      <div className="icard-body">
        <div className="icard-name-row">
          {item.phase && (
            <span className="icard-phase" data-paint={item.paintIndex} title={`Doppler ${item.phase}`}>{item.phase}</span>
          )}
          <span className="icard-name" title={item.name}>{item.displayName || item.name}</span>
        </div>
        {item.collection && (
          <div className="icard-collection" title={item.collection}>{item.collection}</div>
        )}
        <div className="icard-meta">
          {stat && <span className="icard-quality icard-quality--st">ST</span>}
          {item.souvenir && <span className="icard-quality icard-quality--sv">SV</span>}
          {item.wear && WEAR_LABEL[item.wear] && (
            <span className={'icard-wear wear-' + item.wear}>{WEAR_LABEL[item.wear]}</span>
          )}
          {showStats && hasFloat && <span className="icard-float-num">{item.float.toFixed(4)}</span>}
          {showStats && item.paintSeed != null && (
            <span className="icard-pattern-num" title={`Paint seed (pattern): ${item.paintSeed}`}>#{item.paintSeed}</span>
          )}
        </div>
        <div className="icard-row">
          <span className="icard-price">{formatPrice(item.price)}</span>
        </div>
      </div>
    </div>
  );
}

// ─── inventory panel header (used by both sides) ───
function InventoryHead({ label, balance, items, age, onRefresh }) {
  return (
    <div className="invp-head">
      <div className="invp-head-l">
        <div className="invp-label">{label}</div>
        <div className="invp-balance">{formatPrice(balance)}</div>
      </div>
      <div className="invp-head-r">
        <span className="invp-count">{items} {T('itemsCount')}</span>
        {age && <span className="invp-age">{T('pricesAge')} {age}</span>}
        <button className="invp-refresh" onClick={onRefresh} aria-label={T('refresh')}>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
          </svg>
        </button>
      </div>
    </div>
  );
}

// ─── inventory toolbar (search + sort + price-range chips) ───
function InventoryToolbar({ query, setQuery, sort, setSort, range, setRange }) {
  return (
    <div className="invp-toolbar">
      <div className="search-box">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
          <circle cx="11" cy="11" r="7" /><path d="m20 20-3-3" />
        </svg>
        <input value={query} onChange={e => setQuery(e.target.value)} placeholder={T('searchSkins')} />
      </div>
      <button
        type="button"
        className="invp-sort"
        onClick={() => setSort(s => s === 'desc' ? 'asc' : 'desc')}
        title={T('toggleSort')}
      >
        <span>{sort === 'desc' ? T('priceHigh') : T('priceLow')}</span>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="6 9 12 15 18 9"/></svg>
      </button>
      <div className="invp-chips">
        {PRICE_RANGES.map(r => (
          <button key={r.k} className={'chip' + (range === r.k ? ' active' : '')} onClick={() => setRange(r.k)}>{priceRangeLabel(r)}</button>
        ))}
      </div>
    </div>
  );
}

// ─── one full inventory side ───
function InventorySide({ side, label, items, balance, age, selected, onToggle, onRefresh, hdBonusPct, error }) {
  const [query, setQuery] = useState('');
  const [sort, setSort] = useState('desc');
  const [range, setRange] = useState('all');

  const filtered = useMemo(() => {
    const r = PRICE_RANGES.find(x => x.k === range);
    let out = items.filter(i =>
      i.name.toLowerCase().includes(query.toLowerCase()) &&
      i.price >= r.lo && i.price < r.hi
    );
    out.sort((a, b) => sort === 'desc' ? b.price - a.price : a.price - b.price);
    return out;
  }, [items, query, sort, range]);

  return (
    <div className={'invp invp--' + side}>
      <InventoryHead label={label} balance={balance} items={items.length} age={age} onRefresh={onRefresh} />
      <InventoryToolbar query={query} setQuery={setQuery} sort={sort} setSort={setSort} range={range} setRange={setRange} />
      <div className="invp-scroll">
        {error ? (
          <div className="invp-error" role="alert">
            <div className="invp-error-icon" aria-hidden="true">
              <svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <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>
            </div>
            <div className="invp-error-title">{error.title}</div>
            <div className="invp-error-msg">{error.message}</div>
            {onRefresh && (
              <button type="button" className="invp-error-retry" onClick={onRefresh}>{T('retry')}</button>
            )}
          </div>
        ) : items.length === 0 ? (
          <div className="invp-empty">
            {side === 'give' ? T('noItemsShow') : T('botEmpty')}
          </div>
        ) : (
          <div className={'invp-grid invp-grid--' + side}>
            {filtered.map(it => (
              <ItemCard key={it.id} item={it} side={side} selected={selected.includes(it.id)} onClick={() => onToggle(it.id)} hdBonusPct={hdBonusPct} />
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

// ─── center column: filter + trade summary + recent trades ───
function FilterAccordion({ label, open, onToggle, children }) {
  return (
    <div className={'cp-acc' + (open ? ' open' : '')}>
      <button className="cp-acc-l" onClick={onToggle}>
        <span>{label}</span>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 180ms' }}>
          <polyline points="6 9 12 15 18 9"/>
        </svg>
      </button>
      {open && children && <div className="cp-acc-content">{children}</div>}
    </div>
  );
}

function TradeRow({ label, items, total, currency, onRemove, side }) {
  return (
    <div className="cp-section">
      <div className="cp-section-head">{label}</div>
      {items.length === 0 ? (
        <div className="cp-empty">{side === 'recv' ? T('selectFromBot') : T('selectFromYours')}</div>
      ) : (
        <div className="cp-section-list">
          {items.map(it => {
            const r = RARITY[it.rarity] || RARITY.covert;
            const hasFloat = (it.float || 0) > 0;
            const wearLbl = WEAR_LABEL[it.wear];
            const meta = hasFloat ? `${wearLbl} · ${it.float.toFixed(3)}` : (wearLbl || '');
            return (
              <div className="cp-row" key={it.id}>
                <div className="cp-row-th" style={{ borderLeftColor: r.c }}>
                  {it.iconUrl && <img src={it.iconUrl} alt="" loading="lazy" onError={(e)=>{e.target.style.display='none';}} />}
                </div>
                <div className="cp-row-meta">
                  <div className="cp-row-name" style={{ color: r.c }} title={it.name}>{it.displayName || it.name}</div>
                  {meta && <div className="cp-row-wear">{meta}</div>}
                </div>
                <div className="cp-row-p">{formatPrice(it.price)}</div>
                <button className="cp-row-x" onClick={() => onRemove(it.id)}>×</button>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

const RECENT = [
  {
    when: '2h ago', date: '5/9/2026 · 12:14', id: 'TXN-A82F19',
    got:  { name: 'AK-47 | Frontside Misty', wear: 'FT', float: 0.1679, price: 19.56, color: '#EB4B4B' },
    gave: { name: 'Glock-18 | Wasteland', wear: 'FN', float: 0.0563, price: 12.40, color: '#D32CE6' },
    delta: 7.16, deltaLabel: 'balance credited', fee: 0.62,
  },
  {
    when: '6d ago', date: '5/3/2026 · 18:02', id: 'TXN-91C7A0',
    got:  { name: 'AWP | Chromatic Aberration', wear: 'FN', float: 0.0290, price: 52.71, color: '#EB4B4B' },
    gave: { name: 'M4A4 | Buzz Kill', wear: 'MW', float: 0.0975, price: 45.84, color: '#EB4B4B' },
    delta: 6.87, deltaLabel: 'balance credited', fee: 1.78,
  },
  {
    when: '5/1/2026', date: '5/1/2026 · 09:47', id: 'TXN-77B204',
    got:  { name: '★ Survival Knife | Damascus', wear: 'FN', float: 0.0375, price: 215.41, color: '#E4AE39' },
    gave: { name: 'AK-47 | Case Hardened', wear: 'FT', float: 0.1233, price: 208.23, color: '#D32CE6' },
    delta: -7.18, deltaLabel: 'balance used', fee: 8.62,
  },
];

function PriceRangeSlider({ min = 0, max = 1500 }) {
  const [lo, setLo] = useState(0);
  const [hi, setHi] = useState(max);
  const pct = (v) => ((v - min) / (max - min)) * 100;
  return (
    <div className="cp-prs">
      <div className="cp-prs-vals">
        <span>{formatPrice(lo)}</span>
        <span>{formatPrice(hi)}{hi >= max ? '+' : ''}</span>
      </div>
      <div className="cp-prs-track">
        <div
          className="cp-prs-fill"
          style={{ left: pct(lo) + '%', right: (100 - pct(hi)) + '%' }}
        ></div>
        <input
          type="range" min={min} max={max} step="1" value={lo}
          onChange={e => setLo(Math.min(+e.target.value, hi - 1))}
        />
        <input
          type="range" min={min} max={max} step="1" value={hi}
          onChange={e => setHi(Math.max(+e.target.value, lo + 1))}
        />
      </div>
    </div>
  );
}

function RecentTrades({ onClose, trades }) {
  const list = Array.isArray(trades) ? trades : [];
  return (
    <div className="cp-recent">
      <div className="cp-recent-head">
        <span>{T('recentTrades')}</span>
        {onClose && (
          <button type="button" className="cp-recent-x" onClick={onClose} aria-label={T('closeRecentAria')}>×</button>
        )}
      </div>
      <div className="cp-recent-list">
      {list.length === 0 && (
        <div style={{padding:'14px 8px', color:'var(--text-muted, #909090)', fontSize:12}}>{T('noTradesYet')}</div>
      )}
      {list.map((t, i) => (
        <div className="cp-recent-row" key={t.id || i}>
          <div className="cp-recent-top">
            <span className="cp-recent-when">{t.when}</span>
            <span className={'cp-recent-badge ' + (t.statusClass || '')}>{t.statusLabel || T('statusCompleted')}</span>
          </div>
          <div className="cp-recent-pair">
            <div className="cp-recent-leg">
              <span className="cp-recent-leg-lbl">{T('youGot')}</span>
              <div className="cp-recent-leg-skin">
                <span className="cp-recent-leg-thumb" style={{ '--leg-color': t.got.color }}>
                  {t.got.icon && <img src={t.got.icon} alt="" loading="lazy" onError={(e)=>{e.target.style.display='none';}} />}
                </span>
                <span className="cp-recent-leg-name" style={{ color: t.got.color }}>{t.got.name}</span>
              </div>
              <span className="cp-recent-leg-v">{formatPrice(t.got.price)}</span>
            </div>
            <div className="cp-recent-leg">
              <span className="cp-recent-leg-lbl">{T('youGave')}</span>
              <div className="cp-recent-leg-skin">
                <span className="cp-recent-leg-thumb" style={{ '--leg-color': t.gave.color }}>
                  {t.gave.icon && <img src={t.gave.icon} alt="" loading="lazy" onError={(e)=>{e.target.style.display='none';}} />}
                </span>
                <span className="cp-recent-leg-name" style={{ color: t.gave.color }}>{t.gave.name}</span>
              </div>
              <span className="cp-recent-leg-v">{formatPrice(t.gave.price)}</span>
            </div>
          </div>
          <div className="cp-recent-meta">
            <span><b>{t.date}</b></span>
            {t.fee != null && <span>{T('fee')} <b>{formatPrice(t.fee)}</b></span>}
            <span className="cp-recent-id">{t.id}</span>
          </div>
          {t.delta !== 0 && (
            <div className={'cp-recent-delta ' + (t.delta >= 0 ? 'up' : 'dn')}>
              {t.delta >= 0 ? '+' : '−'}{formatPrice(Math.abs(t.delta))} {t.deltaLabel}
            </div>
          )}
        </div>
      ))}
      </div>
    </div>
  );
}

function CenterPanel({ giveItems, recvItems, giveTotal, recvTotal, balance, canSend, onSend, onRemoveGive, onRemoveRecv, recentTrades, botFilter, onBotFilterChange }) {
  const [open, setOpen] = useState({ price: true, lock: false, cat: false, ext: false });
  const [recentOpen, setRecentOpen] = useState(false);
  const toggle = (k) => setOpen(s => ({ ...s, [k]: !s[k] }));
  // Sane defaults if the parent didn't wire filter state
  const f = botFilter || { statTrak: false, souvenir: false, maxLockDays: 8, category: 'any', exterior: 'any' };
  const setFilter = (patch) => onBotFilterChange?.({ ...f, ...patch });

  return (
    <aside className="cp">
      {/* Trade summary (Bot gives / You give / stats) sits at the TOP so the
          user sees what they're committing to before scrolling. Send button
          fires immediately after the math. Filters live below since they're
          browse-time tools, not commit-time tools. */}
      <TradeRow side="recv" label={T('botGivesYou')} items={recvItems} total={recvTotal} onRemove={onRemoveRecv} />
      <TradeRow side="give" label={T('youGive')}     items={giveItems} total={giveTotal} onRemove={onRemoveGive} />

      <div className="cp-fee">{T('feeNote')}</div>

      <div className="cp-stats">
        <div className="cp-stat-line"><span>{T('botAsks')}</span><span className="v">{formatPrice(recvTotal)}</span></div>
        <div className="cp-stat-line"><span>{T('yourItems')}</span><span className="v">{formatPrice(giveTotal)}</span></div>
        <div className="cp-stat-line"><span>{T('yourBalance')}</span><span className="v gold">{formatPrice(balance)}</span></div>
        {(() => {
          // Net balance after trade: overpay credits balance, underpay debits balance.
          const after = balance + (giveTotal - recvTotal);
          const cls = after < 0 ? ' v-low' : '';
          return (
            <div className="cp-stat-line cp-stat-after">
              <span>{T('balanceAfter')}</span>
              <span className={'v gold' + cls}>{formatPrice(after)}</span>
            </div>
          );
        })()}
      </div>

      <button className="cp-send" disabled={!canSend} onClick={onSend}>{T('sendOffer')}</button>

      <FilterAccordion label={T('toggles')} open={open.price} onToggle={() => toggle('price')}>
        <button
          type="button"
          className={'cp-toggle' + (f.statTrak ? ' on' : '')}
          onClick={() => setFilter({ statTrak: !f.statTrak })}
          aria-pressed={f.statTrak}
        >
          <span className="cp-toggle-dot"></span>
          <span className="cp-toggle-lbl">{T('statTrakOnly')}</span>
        </button>
        <button
          type="button"
          className={'cp-toggle' + (f.souvenir ? ' on' : '')}
          onClick={() => setFilter({ souvenir: !f.souvenir })}
          aria-pressed={f.souvenir}
        >
          <span className="cp-toggle-dot"></span>
          <span className="cp-toggle-lbl">{T('souvenirOnly')}</span>
        </button>
      </FilterAccordion>

      <FilterAccordion label={T('tradeLock')} open={open.lock} onToggle={() => toggle('lock')}>
        <div className="cp-lock-range">
          <div className="cp-lock-range-label">
            <span>{T('lessThan')}</span>
            <span className="cp-lock-range-value">
              {f.maxLockDays === 0 ? T('noLock') : `${f.maxLockDays} ${f.maxLockDays === 1 ? T('dayOne') : T('dayMany')}`}
            </span>
          </div>
          <input
            type="range" min="0" max="8" step="1"
            value={f.maxLockDays}
            onChange={e => setFilter({ maxLockDays: parseInt(e.target.value, 10) })}
            aria-label={T('lockSliderAria')}
          />
          <div className="cp-lock-range-ticks"><span>0d</span><span>4d</span><span>8d</span></div>
        </div>
      </FilterAccordion>

      <FilterAccordion label={T('category')} open={open.cat} onToggle={() => toggle('cat')}>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='any'}       onChange={() => setFilter({ category: 'any'       })}/> {T('catAny')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='knife'}     onChange={() => setFilter({ category: 'knife'     })}/> {T('catKnife')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='gloves'}    onChange={() => setFilter({ category: 'gloves'    })}/> {T('catGloves')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='pistol'}    onChange={() => setFilter({ category: 'pistol'    })}/> {T('catPistol')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='smg'}       onChange={() => setFilter({ category: 'smg'       })}/> {T('catSMG')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='rifle'}     onChange={() => setFilter({ category: 'rifle'     })}/> {T('catRifle')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='sniper'}    onChange={() => setFilter({ category: 'sniper'    })}/> {T('catSniper')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='shotgun'}   onChange={() => setFilter({ category: 'shotgun'   })}/> {T('catShotgun')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='mg'}        onChange={() => setFilter({ category: 'mg'        })}/> {T('catMG')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='agent'}     onChange={() => setFilter({ category: 'agent'     })}/> {T('catAgent')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='container'} onChange={() => setFilter({ category: 'container' })}/> {T('catContainer')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='music_kit'} onChange={() => setFilter({ category: 'music_kit' })}/> {T('catMusicKit')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='patch'}     onChange={() => setFilter({ category: 'patch'     })}/> {T('catPatch')}</label>
        <label className="cp-check"><input type="radio" name="bot-cat" checked={f.category==='sticker'}   onChange={() => setFilter({ category: 'sticker'   })}/> {T('catSticker')}</label>
      </FilterAccordion>

      <FilterAccordion label={T('exterior')} open={open.ext} onToggle={() => toggle('ext')}>
        <label className="cp-check"><input type="radio" name="ext" checked={f.exterior==='any'} onChange={() => setFilter({ exterior: 'any' })}/> {T('wearAny')}</label>
        <label className="cp-check"><input type="radio" name="ext" checked={f.exterior==='fn'}  onChange={() => setFilter({ exterior: 'fn'  })}/> {T('wearFN')}</label>
        <label className="cp-check"><input type="radio" name="ext" checked={f.exterior==='mw'}  onChange={() => setFilter({ exterior: 'mw'  })}/> {T('wearMW')}</label>
        <label className="cp-check"><input type="radio" name="ext" checked={f.exterior==='ft'}  onChange={() => setFilter({ exterior: 'ft'  })}/> {T('wearFT')}</label>
        <label className="cp-check"><input type="radio" name="ext" checked={f.exterior==='ww'}  onChange={() => setFilter({ exterior: 'ww'  })}/> {T('wearWW')}</label>
        <label className="cp-check"><input type="radio" name="ext" checked={f.exterior==='bs'}  onChange={() => setFilter({ exterior: 'bs'  })}/> {T('wearBS')}</label>
      </FilterAccordion>

      {recentOpen ? (
        <RecentTrades onClose={() => setRecentOpen(false)} trades={recentTrades} />
      ) : (
        <button type="button" className="cp-recent-reopen" onClick={() => setRecentOpen(true)}>
          {T('showRecent')}
        </button>
      )}
    </aside>
  );
}

// ─── Modal / Toast / Footer (unchanged from before) ───
function TradeUrlModalBody({ initial, onChange }) {
  const [v, setV] = useState(initial || '');
  const [pasted, setPasted] = useState(false);
  const ref = React.useRef(null);
  const setVal = (val) => { setV(val); onChange?.(val); };
  return (
    <div className="tu-modal">
      <p className="tu-modal-desc">{T('tradeUrlPasteDesc')}</p>
      <label className="tu-modal-field">
        <span>{T('tradeUrl')}</span>
        <div className={'tu-modal-input' + (pasted ? ' is-pasted' : '')}>
          <input
            ref={ref}
            value={v}
            placeholder="https://steamcommunity.com/tradeoffer/new/?partner=…&token=…"
            onChange={e => setVal(e.target.value)}
            onPaste={e => {
              const txt = (e.clipboardData || window.clipboardData).getData('text');
              if (txt) { e.preventDefault(); setVal(txt); setPasted(true); setTimeout(() => setPasted(false), 900); }
            }}
            autoFocus
            spellCheck="false"
          />
          <button
            type="button"
            className="tu-modal-paste"
            onClick={async () => {
              try {
                const txt = await navigator.clipboard.readText();
                if (txt) { setVal(txt); setPasted(true); setTimeout(() => setPasted(false), 900); }
              } catch { ref.current?.focus(); }
            }}
          >{T('paste')}</button>
        </div>
      </label>
      <div className="tu-modal-help">
        <a href={(_HZ_LANG === 'en' ? '' : '/' + _HZ_LANG) + '/guides/steam-trade-url-guide'} target="_blank" rel="noopener">{T('whereTradeUrl')}</a>
      </div>
    </div>
  );
}

// Loud blocking error popup — used for serious failures (trade rejection, etc.).
// Single close action, red iconography, can't be dismissed by clicking outside.
function ErrorModal({ title, message, onClose }) {
  return (
    <div className="modal-backdrop modal-backdrop--error">
      <div className="modal-card modal-card--error" onClick={e => e.stopPropagation()}>
        <div className="error-modal-icon" aria-hidden="true">
          <svg viewBox="0 0 24 24" width="44" height="44" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <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>
        </div>
        <div className="modal-head modal-head--error">
          <h2>{title || T('errSomethingWrong')}</h2>
        </div>
        <div className="modal-body modal-body--error">{message}</div>
        <div className="modal-actions modal-actions--error">
          <button className="btn btn-primary btn-error" onClick={onClose} autoFocus>{T('close')}</button>
        </div>
      </div>
    </div>
  );
}

function Modal({ title, body, onCancel, onConfirm, confirmLabel, singleAction = false }) {
  return (
    <div className="modal-backdrop" onClick={onCancel}>
      <div className="modal-card" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><h2>{title}</h2><button onClick={onCancel}>×</button></div>
        <div className="modal-body">{body}</div>
        <div className="modal-actions">
          {!singleAction && <button className="btn btn-ghost" onClick={onCancel}>{T('cancel')}</button>}
          <button className="btn btn-primary" onClick={onConfirm}>{confirmLabel || T('confirm')}</button>
        </div>
      </div>
    </div>
  );
}

function Toast({ message }) {
  return (
    <div className="toast">
      <div className="ic">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
          <path d="M20 6L9 17l-5-5"/>
        </svg>
      </div>
      <div className="msg">{message}</div>
    </div>
  );
}

function Footer({ onContact }) {
  return (
    <footer className="ftr">
      <div className="container ftr-inner">
        <div className="ftr-links">
          <a>{T('ftrFAQ')}</a><a>{T('ftrGuides')}</a><a>{T('ftrPrivacy')}</a><a>{T('ftrTerms')}</a>
          <a onClick={onContact}>{T('ftrContact')}</a>
        </div>
        <div className="ftr-meta">{T('ftrMeta')}</div>
      </div>
    </footer>
  );
}

function ContactModalBody({ invite = 'discord.gg/hostadz', onCopy }) {
  const [copied, setCopied] = useState(false);
  const handleCopy = async () => {
    try { await navigator.clipboard.writeText('https://' + invite); } catch {}
    setCopied(true);
    setTimeout(() => setCopied(false), 1800);
    onCopy && onCopy();
  };
  return (
    <div className="contact-body">
      <p className="contact-lede">{T('contactLede')}</p>
      <div className="contact-card">
        <div className="contact-card-icon" aria-hidden="true">
          <svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor">
            <path d="M19.27 5.33A17.6 17.6 0 0 0 14.86 4l-.22.45a16.3 16.3 0 0 1 4.05 1.32 13.5 13.5 0 0 0-13.39 0A16.3 16.3 0 0 1 9.36 4.45L9.14 4a17.6 17.6 0 0 0-4.41 1.33C2.05 9.4 1.32 13.36 1.68 17.27A17.7 17.7 0 0 0 7.04 20l1.16-1.6a11.1 11.1 0 0 1-1.79-.86l.43-.34a12.6 12.6 0 0 0 10.32 0l.43.34c-.56.34-1.16.62-1.79.86l1.16 1.6a17.7 17.7 0 0 0 5.36-2.73c.42-4.5-.71-8.43-2.05-11.94zM8.52 15.32c-1.05 0-1.92-.97-1.92-2.16 0-1.19.85-2.16 1.92-2.16 1.07 0 1.94.97 1.92 2.16 0 1.19-.85 2.16-1.92 2.16zm6.96 0c-1.05 0-1.92-.97-1.92-2.16 0-1.19.85-2.16 1.92-2.16 1.07 0 1.94.97 1.92 2.16 0 1.19-.85 2.16-1.92 2.16z"/>
          </svg>
        </div>
        <div className="contact-card-text">
          <div className="contact-card-title">{T('contactCommunity')}</div>
          <div className="contact-card-meta">{T('contactMembers')}</div>
        </div>
        <div className="contact-card-link">{invite}</div>
      </div>
      <button type="button" className="contact-copy" onClick={handleCopy}>
        {copied ? T('contactCopied') : T('contactCopyInvite')}
      </button>
    </div>
  );
}

Object.assign(window, {
  Header, ItemCard, InventorySide, CenterPanel, Modal, ErrorModal, TradeUrlModalBody, ContactModalBody, Toast, Footer, RARITY,
  T, _HZ_LANG, _HZ_T,
});
