HuskerDaddy's RR Script

HuskerDaddy's martingale wager helper for Torn Russian Roulette.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         HuskerDaddy's RR Script
// @namespace    codex.torn.rr-helper
// @version      4.0.14
// @description  HuskerDaddy's martingale wager helper for Torn Russian Roulette.
// @license      MIT
// @match        https://www.torn.com/page.php?sid=russianRoulette*
// @match        https://www.torn.com/properties.php*
// @match        https://www.torn.com/trade.php*
// @match        https://www.torn.com/item.php*
// @match        https://www.torn.com/factions.php*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(() => {
  'use strict';

  const STORAGE_KEY = 'torn-rr-helper-settings-v1';
  const defaultState = { enabled: true, inactiveWalletAutofill: true, ghostTradeEnabled: true, quickJoinEnabled: true, hideMultiShotButtons: false, dailyExpenses: 0, forecastOpen: true, settingsOpen: false, turboMultiplier: 1, base: 100000, multiplier: 2, losses: 0, lastOutcome: null, minimized: false, position: null };
  const load = () => {
    try { return { ...defaultState, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') }; }
    catch { return { ...defaultState }; }
  };
  let state = load();
  const save = () => localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
  const money = amount => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(amount);
  const turboMultiplier = () => state.turboMultiplier === 3 ? 3 : state.turboMultiplier === 2 ? 2 : 1;
  const currentBet = () => Math.round(state.base * turboMultiplier() * Math.pow(state.multiplier, state.losses));
  const nextBet = () => Math.round(currentBet() * state.multiplier);
  const isVisible = element => !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
  const isRussianRoulettePage = new URL(location.href).searchParams.get('sid') === 'russianRoulette';
  const isVaultPage = location.pathname.endsWith('/properties.php') && location.hash.includes('tab=vault');
  const isGhostTradePage = location.pathname.endsWith('/trade.php');
  const isMedicalItemsPage = location.pathname.endsWith('/item.php') || location.pathname.endsWith('/factions.php');
  const MEDICAL_STORE = 'torn-rr-medical-cooldown-v1';

  if (!isRussianRoulettePage && !isVaultPage && !isGhostTradePage && !isMedicalItemsPage) return;
  if (window.__tornRRHelperInstalled) return;
  window.__tornRRHelperInstalled = true;

  const autofillStyle = document.createElement('style');
  autofillStyle.textContent = '.rr-helper-autofill-flash { border-color: #41b6ff !important; box-shadow: 0 0 0 2px rgba(65,182,255,.55) !important; transition: border-color .15s ease, box-shadow .15s ease; } .rr-helper-quick-confirm { animation: none !important; transition: none !important; opacity: 1 !important; visibility: visible !important; } #rr-helper-join-proxy { position: fixed; z-index: 1000000; min-width: 58px; min-height: 28px; border: 0; border-radius: 4px; color: #fff; background: #3d4958; font: 700 14px/1 system-ui, sans-serif; cursor: pointer; transform: translate(-50%, -50%) scale(1.5); touch-action: manipulation; } #rr-helper-join-proxy:active { background: #526277; } body.rr-helper-hide-multishot button.commitButton___AZapi[data-id="2"], body.rr-helper-hide-multishot button.commitButton___AZapi[data-id="3"], body.rr-helper-hide-multishot button[class*="commitButton"][data-id="2"], body.rr-helper-hide-multishot button[class*="commitButton"][data-id="3"] { display: none !important; }';
  document.head.append(autofillStyle);
  const autofillFlashTimers = new WeakMap();
  const autofillFlashedValues = new Map();
  function autofillFieldKey(input) {
    const form = input.closest('form');
    return [location.pathname, location.hash, input.getAttribute('aria-label'), form?.className, input.name, input.type].filter(Boolean).join('|');
  }
  function flashAutofilledInput(input, value) {
    const key = autofillFieldKey(input);
    const formattedValue = String(value);
    if (autofillFlashedValues.get(key) === formattedValue) return;
    autofillFlashedValues.set(key, formattedValue);
    clearTimeout(autofillFlashTimers.get(input));
    input.classList.remove('rr-helper-autofill-flash');
    void input.offsetWidth;
    input.classList.add('rr-helper-autofill-flash');
    autofillFlashTimers.set(input, setTimeout(() => input.classList.remove('rr-helper-autofill-flash'), 650));
  }

  function setInputValue(input, value) {
    const currentNumber = Number(String(input.value).replace(/[^\d.-]/g, ''));
    const nextNumber = Number(String(value).replace(/[^\d.-]/g, ''));
    if (Number.isFinite(currentNumber) && Number.isFinite(nextNumber) && currentNumber === nextNumber) return false;
    const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
    setter ? setter.call(input, value) : (input.value = value);
    input.dispatchEvent(new Event('input', { bubbles: true }));
    input.dispatchEvent(new Event('change', { bubbles: true }));
    flashAutofilledInput(input, value);
    return true;
  }

  if (isMedicalItemsPage) {
    let lastMedicalEndTime = 0;
    let lastMedicalSignature = '';
    const saveMedicalCooldown = (seconds, signature) => {
      if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 14 * 86400 || signature === lastMedicalSignature) return;
      lastMedicalSignature = signature;
      const endTime = Date.now() + seconds * 1000;
      // A live countdown changes its displayed clock every second, but its end
      // time stays effectively the same. Static confirmations are saved once.
      if (Math.abs(endTime - lastMedicalEndTime) < 2000) return;
      lastMedicalEndTime = endTime;
      localStorage.setItem(MEDICAL_STORE, String(endTime));
    };
    const captureMedicalCooldown = () => {
      const counters = document.querySelectorAll('.response-wrap .counter-wrap, .use-wrap .counter-wrap, [data-seconds-left], [data-time], .counter-wrap');
      counters.forEach(counter => {
        const message = counter.closest('p, form, .response-wrap, .use-wrap')?.textContent || '';
        if (!/medical cooldown/i.test(message)) return;
        const clock = String(counter.textContent || '').trim().match(/^(\d{1,3}):(\d{2}):(\d{2})$/);
        const visibleSeconds = clock ? Number(clock[1]) * 3600 + Number(clock[2]) * 60 + Number(clock[3]) : NaN;
        const seconds = Number(counter.dataset.secondsLeft || visibleSeconds || counter.dataset.time || 0);
        saveMedicalCooldown(seconds, `${message}|${counter.textContent}|${counter.dataset.secondsLeft || counter.dataset.time || ''}`);
      });

      // The confirmation wording differs between medical items and between
      // desktop/PDA. Fall back to any displayed medical-cooldown clock.
      const pageText = document.body?.innerText || document.body?.textContent || '';
      const textMatch = pageText.match(/medical\s+cooldown[\s\S]{0,80}?(\d{1,3}):(\d{2}):(\d{2})/i);
      if (!textMatch) return;
      const seconds = Number(textMatch[1]) * 3600 + Number(textMatch[2]) * 60 + Number(textMatch[3]);
      saveMedicalCooldown(seconds, textMatch[0]);
    };
    captureMedicalCooldown();
    new MutationObserver(captureMedicalCooldown).observe(document.documentElement, { childList: true, subtree: true, characterData: true, attributes: true });
    return;
  }

  if (isVaultPage) {
    let vaultFillQueued = false;
    let vaultRetryTimers = [];
    const vaultToggleStyle = document.createElement('style');
    vaultToggleStyle.textContent = `
      #rr-vault-toggle { cursor: pointer; }
      #rr-vault-toggle .p-icon { display: inline-flex; align-items: center; justify-content: center; color: #f3f3f3; background: #777; font-family: inherit; font-size: 11px; font-weight: 700; line-height: 1; text-align: center; }
    `;
    document.head.append(vaultToggleStyle);
    const renderVaultToggle = () => {
      const button = document.querySelector('#rr-vault-toggle');
      if (!button) return;
      button.classList.toggle('is-off', !state.enabled);
      const pressed = String(state.enabled);
      const title = `RR Helper: ${state.enabled ? 'On' : 'Off'}`;
      if (button.getAttribute('aria-pressed') !== pressed) button.setAttribute('aria-pressed', pressed);
      if (button.title !== title) button.title = title;
      const icon = button.querySelector('.p-icon');
      const description = button.querySelector('.desc');
      if (icon && icon.textContent !== 'RR') icon.textContent = 'RR';
      const label = `RR Tracker: ${state.enabled ? 'On' : 'Off'}`;
      if (description && description.textContent !== label) description.textContent = label;
    };
    const installVaultToggle = () => {
      if (document.querySelector('#rr-vault-toggle')) { renderVaultToggle(); return; }
      const button = document.querySelector('li.kick-prop');
      if (!button) return;
      button.id = 'rr-vault-toggle';
      button.setAttribute('role', 'button');
      button.dataset.rrTrackerBound = 'true';
      button.addEventListener('click', event => {
        event.preventDefault();
        event.stopPropagation();
        state.enabled = !state.enabled;
        save();
        renderVaultToggle();
        scheduleVaultFill();
      });
      renderVaultToggle();
    };
    const fillVaultAmount = () => {
      const depositInput = [...document.querySelectorAll('form.deposit-box input.input-money[type="text"][data-deposit]')].find(isVisible);
      const displayedWallet = Number(String(document.querySelector('#vault-dvalue')?.textContent || '').replace(/[^\d.-]/g, ''));
      const depositedWallet = Number(String(depositInput?.dataset.deposit || '').replace(/[^\d.-]/g, ''));
      const walletAmounts = [displayedWallet, depositedWallet].filter(amount => Number.isFinite(amount) && amount >= 0);
      const walletAmount = walletAmounts.length ? Math.max(...walletAmounts) : NaN;
      const filledDeposit = !!(depositInput && Number.isFinite(walletAmount));
      if (filledDeposit) setInputValue(depositInput, String(walletAmount));
      // Depositing all on-hand cash is safety-related and stays enabled even
      // when bet autofill is paused.
      if (!state.enabled) return filledDeposit;

      const withdrawalInput = [...document.querySelectorAll('input.input-money[type="text"][data-money]')]
        .find(input => isVisible(input) && !input.closest('form.deposit-box'));
      if (withdrawalInput) setInputValue(withdrawalInput, String(currentBet()));
      return filledDeposit || !!withdrawalInput;
    };
    const scheduleVaultFill = () => {
      if (vaultFillQueued) return;
      vaultFillQueued = true;
      setTimeout(() => { vaultFillQueued = false; fillVaultAmount(); }, 50);
    };
    const retryVaultFill = () => {
      vaultRetryTimers.forEach(clearTimeout);
      vaultRetryTimers = [0, 250, 750].map(delay => setTimeout(() => {
        state = load();
        fillVaultAmount();
        installVaultToggle();
      }, delay));
    };
    retryVaultFill();
    new MutationObserver(() => { scheduleVaultFill(); installVaultToggle(); }).observe(document.documentElement, { childList: true, subtree: true, characterData: true });
    window.addEventListener('focus', retryVaultFill);
    document.addEventListener('visibilitychange', () => { if (!document.hidden) retryVaultFill(); });
    window.addEventListener('storage', event => {
      if (event.key !== STORAGE_KEY) return;
      state = load();
      renderVaultToggle();
      scheduleVaultFill();
    });
    return;
  }

  if (isGhostTradePage) {
    let ghostTradeId = null;
    let ghostTradeBalance = null;
    let ghostTradeOutcome = null;
    let ghostTradeTarget = null;
    let ghostTradeMode = null;
    let ghostTradeBet = null;
    let ghostTradeFillQueued = false;
    const isAddMoneyRoute = () => location.hash.includes('step=addmoney');
    const currentTradeId = () => new URLSearchParams(location.hash.slice(1)).get('ID') || location.hash;
    const findGhostTradeInput = () => [...document.querySelectorAll('input.user-id.input-money[data-money]')].find(isVisible);
    const findAvailableCash = () => {
      const amount = [...document.querySelectorAll('p .money-value')].find(element => element.closest('p')?.textContent.includes('You have'));
      return amount ? Number(String(amount.textContent).replace(/[^\d.-]/g, '')) : null;
    };
    const findWalletCash = () => {
      const wallet = document.querySelector('#user-money[data-money]');
      const amount = Number(String(wallet?.dataset.money || '').replace(/[^\d.-]/g, ''));
      return Number.isFinite(amount) ? amount : null;
    };
    const fillGhostTradeAmount = () => {
      state = load();
      if (!state.enabled || !state.ghostTradeEnabled || !isAddMoneyRoute()) return false;
      const input = findGhostTradeInput();
      if (!input) return false;
      const tradeId = currentTradeId();
      const reportedBalance = Number(String(input.dataset.money || '').replace(/[^\d.-]/g, ''));
      if (ghostTradeMode === 'deposit' && Number.isFinite(reportedBalance) && reportedBalance === ghostTradeTarget) {
        ghostTradeBalance = reportedBalance;
        ghostTradeTarget = ghostTradeBalance;
        ghostTradeMode = 'protected';
        ghostTradeBet = currentBet();
        state.lastOutcome = 'ready';
        ghostTradeOutcome = state.lastOutcome;
        save();
      }
      if (tradeId !== ghostTradeId || state.lastOutcome !== ghostTradeOutcome || (ghostTradeMode !== 'protected' && currentBet() !== ghostTradeBet) || !Number.isFinite(ghostTradeTarget)) {
        ghostTradeId = tradeId;
        ghostTradeBalance = reportedBalance;
        ghostTradeOutcome = state.lastOutcome;
        if (!Number.isFinite(ghostTradeBalance)) return false;
        const availableCash = findAvailableCash();
        // Torn can leave the "You have" line stale while this screen stays open.
        // The header wallet is safe to use only immediately after a detected win;
        // never use it when calculating the next withdrawal.
        const walletCash = state.lastOutcome === 'win' ? findWalletCash() : null;
        const depositCash = state.lastOutcome === 'win'
          ? Math.max(Number.isFinite(availableCash) ? availableCash : 0, Number.isFinite(walletCash) ? walletCash : 0)
          : availableCash;
        if (state.lastOutcome === null && !Number.isFinite(depositCash)) return false;
        const shouldDeposit = Number.isFinite(depositCash) && depositCash > 0 && (state.lastOutcome === 'win' || state.lastOutcome === null);
        if (shouldDeposit) {
          ghostTradeTarget = ghostTradeBalance + depositCash;
          ghostTradeMode = 'deposit';
        } else {
          ghostTradeTarget = Math.max(0, ghostTradeBalance - currentBet());
          ghostTradeMode = 'withdraw';
        }
        ghostTradeBet = currentBet();
      }
      if (!Number.isFinite(ghostTradeTarget)) return false;
      setInputValue(input, String(ghostTradeTarget));
      return true;
    };
    const scheduleGhostTradeFill = () => {
      if (ghostTradeFillQueued) return;
      ghostTradeFillQueued = true;
      setTimeout(() => { ghostTradeFillQueued = false; fillGhostTradeAmount(); }, 50);
    };
    fillGhostTradeAmount();
    new MutationObserver(scheduleGhostTradeFill).observe(document.documentElement, { childList: true, subtree: true });
    setInterval(() => { state = load(); fillGhostTradeAmount(); }, 300);
    window.addEventListener('hashchange', () => {
      if (ghostTradeMode === 'deposit' && location.hash.includes('step=view')) {
        state.lastOutcome = 'ready';
        save();
      }
      ghostTradeId = null;
      ghostTradeBalance = null;
      ghostTradeOutcome = null;
      ghostTradeTarget = null;
      ghostTradeMode = null;
      ghostTradeBet = null;
      scheduleGhostTradeFill();
    });
    window.addEventListener('storage', event => {
      if (event.key !== STORAGE_KEY) return;
      state = load();
      scheduleGhostTradeFill();
    });
    return;
  }

  function findWagerInput() {
    const selectors = [
      'input[aria-label="Money value"]',
      'input[name*="bet" i]',
      'input[id*="bet" i]',
      'input[name*="wager" i]',
      'input[id*="wager" i]',
      'input[placeholder*="amount" i]',
      'input[type="number"]'
    ];
    const candidates = [...document.querySelectorAll(selectors.join(','))]
      .filter(input => !input.closest('#rr-helper-root') && !input.disabled && !input.readOnly && isVisible(input));
    return candidates[0] || null;
  }

  function currentWalletCash() {
    const wallet = document.querySelector('#user-money[data-money]');
    const storedAmount = Number(String(wallet?.dataset.money || '').replace(/[^\d.-]/g, ''));
    if (Number.isFinite(storedAmount)) return storedAmount;
    const visibleWallet = document.querySelector('#user-money');
    const visibleAmount = Number(String(visibleWallet?.textContent || '').replace(/[^\d.-]/g, ''));
    return Number.isFinite(visibleAmount) ? visibleAmount : null;
  }

  function fillWager() {
    // The Vault toggle can change this setting in another tab, so always use
    // the current saved state before choosing wallet vs. martingale amount.
    state = load();
    if (!state.enabled && !state.inactiveWalletAutofill) return false;
    const input = findWagerInput();
    if (!input) return false;
    const amount = state.enabled ? currentBet() : currentWalletCash();
    if (!Number.isFinite(amount) || amount < 0) return false;
    const value = String(amount);
    setInputValue(input, value);
    return true;
  }

  let wagerObserver = null;
  function fillWagerWhenAvailable() {
    state = load();
    if (!state.enabled && !state.inactiveWalletAutofill) { wagerObserver?.disconnect(); wagerObserver = null; return; }
    if (fillWager()) { wagerObserver?.disconnect(); wagerObserver = null; return; }
    if (wagerObserver) return;
    wagerObserver = new MutationObserver(() => {
      if (fillWager()) { wagerObserver.disconnect(); wagerObserver = null; }
    });
    wagerObserver.observe(document.documentElement, { childList: true, subtree: true });
  }

  function retryWagerFill() {
    [0, 180, 500].forEach(delay => setTimeout(() => {
      state = load();
      fillWagerWhenAvailable();
    }, delay));
  }

  const root = document.createElement('div');
  root.id = 'rr-helper-root';
  root.innerHTML = `
    <style>
      #rr-helper-root { position: fixed; z-index: 2147483647; right: 18px; bottom: 18px; color: #e8edf6; font: 14px/1.35 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
      #rr-helper-root * { box-sizing: border-box; }
      #rr-helper-root .rr-card { width: 310px; overflow: hidden; border: 1px solid rgba(255,255,255,.16); border-radius: 12px; background: #1b2029; box-shadow: 0 12px 38px rgba(0,0,0,.38); }
      #rr-helper-root .rr-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 12px; border-bottom: 1px solid rgba(255,255,255,.11); cursor: grab; touch-action: none; }
      #rr-helper-root .rr-head:active { cursor: grabbing; }
      #rr-helper-root .rr-brand { display: inline-flex; align-items: center; gap: 5px; min-width: 0; white-space: nowrap; }
      #rr-helper-root .rr-logo { display: block; width: 42px; height: 28px; object-fit: contain; }
      #rr-helper-root .rr-head > .rr-medical { font-size: 14px; white-space: nowrap; }
      #rr-helper-root .rr-actions { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
      #rr-helper-root button { border: 0; border-radius: 7px; padding: 7px 9px; color: inherit; background: #303846; font: inherit; cursor: pointer; }
      #rr-helper-root button:hover { background: #3a4556; }
      #rr-helper-root button[aria-pressed="true"] { color: #07170b; background: #7de095; }
      #rr-helper-root button[aria-pressed="false"] { color: #f5d9d9; background: #69343b; }
      #rr-helper-root .rr-minimize { min-width: 27px; padding-inline: 8px; }
      #rr-helper-root .rr-head .rr-actions button { padding: 6px 8px; font-size: 12px; }
      #rr-helper-root .rr-head .rr-actions .rr-minimize { min-width: 28px; padding-inline: 7px; }
      #rr-helper-root .rr-body { padding: 14px; }
      #rr-helper-root .rr-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
      #rr-helper-root label { display: block; margin-bottom: 5px; color: #aeb8c8; font-size: 12px; }
      #rr-helper-root input { width: 100%; min-width: 0; border: 1px solid #495568; border-radius: 7px; padding: 8px; color: #edf2fb; background: #11151c; font: inherit; }
      #rr-helper-root input:disabled { opacity: .55; }
      #rr-helper-root .rr-amounts { margin: 14px 0 0; border-top: 1px solid rgba(255,255,255,.11); }
      #rr-helper-root .rr-line { display: flex; justify-content: space-between; gap: 10px; padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,.11); }
      #rr-helper-root .rr-line span { color: #aeb8c8; }
      #rr-helper-root .rr-profit-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 10px; }
      #rr-helper-root .rr-profit-row span { color: #aeb8c8; }
      #rr-helper-root .rr-profit-input { width: 122px; border: 1px solid #495568; border-radius: 7px; padding: 7px 8px; color: #edf2fb; background: #11151c; font: inherit; text-align: right; }
      #rr-helper-root .rr-expense-row { padding-top: 7px; }
      #rr-helper-root .rr-forecast { margin-top: 14px; border-top: 1px solid rgba(255,255,255,.11); border-bottom: 1px solid rgba(255,255,255,.11); padding: 10px 0; }
      #rr-helper-root .rr-forecast-toggle { width: 100%; padding: 0; color: #aeb8c8; background: transparent; text-align: left; font-size: 12px; }
      #rr-helper-root .rr-forecast-toggle:hover { background: transparent; color: #edf2fb; }
      #rr-helper-root .rr-forecast-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); overflow: hidden; border: 1px solid #394351; border-radius: 7px; background: #151a21; }
      #rr-helper-root .rr-forecast-list[hidden] { display: none !important; }
      #rr-helper-root .rr-forecast-item { display: flex; align-items: center; gap: 3px; min-width: 0; padding: 4px 5px; border-right: 1px solid rgba(255,255,255,.07); border-bottom: 1px solid rgba(255,255,255,.07); font-size: 9px; font-variant-numeric: tabular-nums; white-space: nowrap; }
      #rr-helper-root .rr-forecast-item:nth-child(3n) { border-right: 0; }
      #rr-helper-root .rr-forecast-item:last-child { border-bottom: 0; }
      #rr-helper-root .rr-forecast-item:last-child:nth-child(3n + 1) { grid-column: 2; }
      #rr-helper-root .rr-forecast-item span { color: #aeb8c8; }
      #rr-helper-root .rr-forecast-item strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      #rr-helper-root .rr-forecast-win { overflow: hidden; color: #7de095; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
      #rr-helper-root .rr-forecast-win.is-loss { color: #f28b8b; }
      #rr-helper-root .rr-settings-toggle { width: 100%; margin-top: 12px; text-align: left; }
      #rr-helper-root .rr-settings-toggle[aria-expanded="true"] { background: #3a4556; }
      #rr-helper-root .rr-settings { padding-top: 12px; }
      #rr-helper-root .rr-turbo-row { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-top: 10px; }
      #rr-helper-root .rr-turbo.is-active { color: #201600; background: #f4cc54; }
      #rr-helper-root .rr-wallet-autofill { display: flex; align-items: center; gap: 8px; margin-top: 11px; color: #aeb8c8; font-size: 12px; }
      #rr-helper-root .rr-wallet-autofill input { width: auto; margin: 0; padding: 0; accent-color: #7de095; }
      #rr-helper-root .rr-pill { display: none; align-items: center; gap: 5px; border: 1px solid rgba(255,255,255,.16); border-radius: 999px; padding: 5px; color: #edf2fb; background: #1b2029; box-shadow: 0 8px 26px rgba(0,0,0,.32); }
      #rr-helper-root .rr-pill-main { border-radius: 999px; padding: 4px 7px; color: inherit; background: transparent; }
      #rr-helper-root .rr-pill .rr-turbo { padding: 4px 6px; font-size: 12px; }
      #rr-helper-root .rr-pill .rr-turbo.is-active { color: #201600; background: #f4cc54; }
      #rr-helper-root .rr-medical { color: #aeb8c8; font-variant-numeric: tabular-nums; }
      #rr-helper-root .rr-profit { color: #aeb8c8; font-variant-numeric: tabular-nums; }
      @media (max-width: 340px) {
        #rr-helper-root { right: 2px; }
        #rr-helper-root .rr-card { width: min(310px, calc(100vw - 4px)); }
        #rr-helper-root .rr-head { gap: 4px; padding: 8px; }
        #rr-helper-root .rr-medical { font-size: 12px; }
        #rr-helper-root .rr-actions { gap: 4px; }
        #rr-helper-root .rr-actions button { padding: 5px 6px; font-size: 10px; }
        #rr-helper-root .rr-actions .rr-minimize { min-width: 24px; padding-inline: 6px; }
      }
      #rr-helper-anchor { padding: 0; color: inherit; font: inherit; }
      #rr-helper-anchor { display: inline-flex !important; flex-direction: row; align-items: center; gap: 5px; white-space: nowrap; }
      #rr-helper-anchor button { border: 0; color: inherit; font: inherit; cursor: pointer; }
      #rr-helper-anchor #rr-anchor-expand { display: inline-flex; align-items: center; gap: 5px; padding: 0; background: transparent; }
      #rr-helper-anchor .rr-anchor-turbo { padding: 2px 4px; border-radius: 4px; background: rgba(255,255,255,.12); font-size: 12px; }
      #rr-helper-anchor .rr-anchor-turbo.is-active { color: #201600; background: #f4cc54; }
      #rr-helper-anchor #rr-helper-anchor-text { display: inline !important; white-space: nowrap; }
      #rr-helper-anchor #rr-medical-compact { display: inline !important; white-space: nowrap; color: #8fd3ff; font-variant-numeric: tabular-nums; }
      #rr-helper-anchor #rr-anchor-bets { display: inline-flex; align-items: center; gap: 5px; }
      #rr-helper-anchor #rr-anchor-meta { display: inline-flex; align-items: center; gap: 5px; }
      #rr-helper-root.is-minimized .rr-card { display: none; }
      #rr-helper-root.is-minimized .rr-pill { display: inline-flex; }
      #rr-helper-root.is-minimized.is-anchored .rr-pill { display: none; }
    </style>
    <div class="rr-pill"><button class="rr-pill-main" id="rr-expand" type="button" aria-label="Open Russian Roulette helper"><span class="rr-profit" id="rr-profit-fixed">$0</span> · <span class="rr-medical" id="rr-medical-fixed">--:--:--</span> · &#127922; <span id="rr-fixed-bets"></span></button><button class="rr-turbo" id="rr-turbo-2-pill" type="button" title="Start an x2 base-bet cycle">⚡</button><button class="rr-turbo" id="rr-turbo-3-pill" type="button" title="Start an x3 base-bet cycle">💣</button></div>
    <section class="rr-card" aria-label="Russian Roulette bet settings">
      <header class="rr-head"><span class="rr-brand"><img class="rr-logo" src="data:image/png;base64,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" alt="HD logo"></span><span class="rr-medical" id="rr-medical-full">--:--:--</span><span class="rr-actions"><button id="rr-power" type="button" aria-pressed="true">Active</button><button id="rr-reset" type="button" title="Reset chase to the base bet">Reset</button><button class="rr-minimize" id="rr-minimize" type="button" aria-label="Minimize helper">−</button></span></header>
      <div class="rr-body">
        <div class="rr-fields"><div><label for="rr-base">Base bet</label><input id="rr-base" type="number" min="1" step="1" inputmode="numeric"></div><div><label for="rr-multiplier">Multiplier</label><input id="rr-multiplier" type="number" min="1" step="0.1" inputmode="decimal"></div></div>
        <div class="rr-turbo-row"><button class="rr-turbo" id="rr-turbo-2" type="button" title="Start an x2 base-bet cycle">⚡ x2 cycle</button><button class="rr-turbo" id="rr-turbo-3" type="button" title="Start an x3 base-bet cycle">💣 x3 cycle</button></div>
        <div class="rr-amounts"><div class="rr-line"><span>Current bet</span><strong id="rr-current"></strong></div><div class="rr-line"><span>Next bet</span><strong id="rr-next"></strong></div></div>
        <div class="rr-profit-row"><span>Daily P/L</span><input class="rr-profit-input" id="rr-profit-editor" type="text" inputmode="numeric" aria-label="Daily profit and loss"></div>
        <div class="rr-profit-row rr-expense-row"><span>Daily expenses</span><input class="rr-profit-input" id="rr-expense-editor" type="text" inputmode="numeric" aria-label="Daily expenses"></div>
        <div class="rr-forecast"><button class="rr-forecast-toggle" id="rr-forecast-toggle" type="button" aria-expanded="true">Next 25 games if each game loses ▾</button><div class="rr-forecast-list" id="rr-forecast-list"></div></div>
        <button class="rr-settings-toggle" id="rr-settings-toggle" type="button" aria-expanded="false">Settings ▸</button>
        <div class="rr-settings" id="rr-settings" hidden>
          <label class="rr-wallet-autofill"><input id="rr-wallet-autofill" type="checkbox"> Wallet autofill while inactive</label>
          <label class="rr-wallet-autofill"><input id="rr-ghost-trade-toggle" type="checkbox"> Enable Ghost Trade</label>
          <label class="rr-wallet-autofill"><input id="rr-quick-join-toggle" type="checkbox"> Quick Join games</label>
          <label class="rr-wallet-autofill"><input id="rr-multishot-toggle" type="checkbox"> Hide x2 / x3 buttons</label>
        </div>
      </div>
    </section>`;
  document.documentElement.append(root);

  const ui = {
    power: root.querySelector('#rr-power'), reset: root.querySelector('#rr-reset'), base: root.querySelector('#rr-base'), multiplier: root.querySelector('#rr-multiplier'), turbo2: root.querySelector('#rr-turbo-2'), turbo3: root.querySelector('#rr-turbo-3'), turbo2Pill: root.querySelector('#rr-turbo-2-pill'), turbo3Pill: root.querySelector('#rr-turbo-3-pill'),
    current: root.querySelector('#rr-current'), next: root.querySelector('#rr-next'), pill: root.querySelector('.rr-pill'), fixedBets: root.querySelector('#rr-fixed-bets'),
    medicalFull: root.querySelector('#rr-medical-full'), medicalFixed: root.querySelector('#rr-medical-fixed'), walletAutofill: root.querySelector('#rr-wallet-autofill'), ghostTradeToggle: root.querySelector('#rr-ghost-trade-toggle'), quickJoinToggle: root.querySelector('#rr-quick-join-toggle'), multiShotToggle: root.querySelector('#rr-multishot-toggle'),
    profitEditor: root.querySelector('#rr-profit-editor'), expenseEditor: root.querySelector('#rr-expense-editor'), profitFixed: root.querySelector('#rr-profit-fixed'), forecastToggle: root.querySelector('#rr-forecast-toggle'), forecastList: root.querySelector('#rr-forecast-list'), settingsToggle: root.querySelector('#rr-settings-toggle'), settings: root.querySelector('#rr-settings')
  };
  const anchoredPill = document.createElement('span');
  anchoredPill.id = 'rr-helper-anchor';
  anchoredPill.innerHTML = '<button id="rr-anchor-expand" type="button" aria-label="Open Russian Roulette helper"><span id="rr-anchor-meta"><span id="rr-profit-compact">$0</span><span id="rr-medical-compact">--:--:--</span></span><span id="rr-anchor-bets"><span id="rr-helper-dice">&#127922;</span><span id="rr-helper-anchor-text"></span></span></button><button class="rr-anchor-turbo" id="rr-anchor-turbo-2" type="button" title="Start an x2 base-bet cycle">⚡</button><button class="rr-anchor-turbo" id="rr-anchor-turbo-3" type="button" title="Start an x3 base-bet cycle">💣</button>';
  let anchorObserver = null;
  let hiddenTitle = null;
  let titleDisplay = '';

  const rrNavStyle = document.createElement('style');
  rrNavStyle.textContent = `
    #rr-helper-nav { position: relative; display: inline-flex; align-items: center; }
    #rr-helper-nav-toggle { border: 0; border-radius: 5px; padding: 4px 7px; color: inherit; background: transparent; font: 19px/1 sans-serif; cursor: pointer; }
    #rr-helper-nav-toggle:hover, #rr-helper-nav-toggle[aria-expanded="true"] { background: rgba(255,255,255,.12); }
    #rr-helper-nav-list { position: absolute; top: calc(100% + 6px); right: 0; z-index: 2147483646; display: none; min-width: 148px; overflow: hidden; border: 1px solid rgba(255,255,255,.2); border-radius: 7px; background: #242a33; box-shadow: 0 8px 22px rgba(0,0,0,.38); }
    #rr-helper-nav.is-open #rr-helper-nav-list { display: block; }
    #rr-helper-nav-list a { display: block; padding: 9px 12px; color: inherit; text-decoration: none; white-space: nowrap; }
    #rr-helper-nav-list a:hover { background: rgba(255,255,255,.12); }
  `;
  document.head.append(rrNavStyle);
  let rrNavFillQueued = false;
  const menuLabel = label => label === 'Back to Casino' ? 'Back To Casino' : label;

  function installRrNavigationMenu() {
    const container = document.querySelector('[class*="linksContainer"]');
    if (!container) return;
    const links = [...container.querySelectorAll('a[role="button"]')];
    if (links.length < 3) return;
    const menuLinks = links.map(link => ({ href: link.href, label: link.textContent.trim() }));
    const existingMenu = container.querySelector('#rr-helper-nav');
    if (existingMenu) {
      const items = [...existingMenu.querySelectorAll('#rr-helper-nav-list a')];
      menuLinks.forEach(({ href, label }, index) => {
        const item = items[index];
        if (!item) return;
        const displayLabel = menuLabel(label);
        if (item.href !== href) item.href = href;
        if (item.textContent !== displayLabel) item.textContent = displayLabel;
      });
      links.forEach(link => { link.style.display = 'none'; });
      return;
    }
    links.forEach(link => { link.style.display = 'none'; });
    const menu = document.createElement('div');
    menu.id = 'rr-helper-nav';
    menu.innerHTML = '<button id="rr-helper-nav-toggle" type="button" aria-label="Russian Roulette navigation" aria-expanded="false">&#9776;</button><div id="rr-helper-nav-list"></div>';
    const list = menu.querySelector('#rr-helper-nav-list');
    menuLinks.forEach(({ href, label }) => {
      const item = document.createElement('a');
      item.href = href;
      item.target = '_self';
      item.rel = 'noreferrer';
      item.textContent = menuLabel(label);
      list.append(item);
    });
    const toggle = menu.querySelector('#rr-helper-nav-toggle');
    const closeMenu = () => {
      menu.classList.remove('is-open');
      toggle.setAttribute('aria-expanded', 'false');
    };
    toggle.addEventListener('click', event => {
      event.stopPropagation();
      const open = menu.classList.toggle('is-open');
      toggle.setAttribute('aria-expanded', String(open));
    });
    list.addEventListener('click', closeMenu);
    document.addEventListener('click', event => {
      if (menu.contains(event.target)) return;
      closeMenu();
    });
    container.append(menu);
  }

  function scheduleRrNavigationMenu() {
    if (rrNavFillQueued) return;
    rrNavFillQueued = true;
    setTimeout(() => { rrNavFillQueued = false; installRrNavigationMenu(); }, 50);
  }

  function refreshRrNavigationAfterRouteChange() {
    scheduleRrNavigationMenu();
    // Torn finishes replacing the lobby header shortly after the URL changes.
    setTimeout(installRrNavigationMenu, 250);
    setTimeout(installRrNavigationMenu, 800);
  }

  function findRussianRouletteTitle() {
    return document.querySelector('h4[class*="title"]');
  }

  function syncMinimizedAnchor() {
    if (!state.minimized) {
      if (hiddenTitle?.isConnected) hiddenTitle.style.display = titleDisplay;
      hiddenTitle = null;
      anchoredPill.remove();
      root.classList.remove('is-anchored');
      anchorObserver?.disconnect();
      anchorObserver = null;
      return;
    }
    const title = findRussianRouletteTitle();
    if (!title) {
      root.classList.remove('is-anchored');
      if (!anchorObserver) {
        anchorObserver = new MutationObserver(() => {
          if (!findRussianRouletteTitle()) return;
          anchorObserver.disconnect();
          anchorObserver = null;
          syncMinimizedAnchor();
        });
        anchorObserver.observe(document.documentElement, { childList: true, subtree: true });
      }
      return;
    }
    anchorObserver?.disconnect();
    anchorObserver = null;
    if (hiddenTitle !== title) {
      if (hiddenTitle?.isConnected) hiddenTitle.style.display = titleDisplay;
      hiddenTitle = title;
      titleDisplay = title.style.display;
    }
    title.style.display = 'none';
    anchoredPill.className = '';
    if (anchoredPill.parentElement !== title.parentElement || anchoredPill.nextElementSibling !== title) title.before(anchoredPill);
    root.classList.add('is-anchored');
  }

  let medicalEndTime = Number(localStorage.getItem(MEDICAL_STORE)) || 0;

  function formatMedicalTime(seconds) {
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const remainingSeconds = seconds % 60;
    return [hours, minutes, remainingSeconds].map(value => String(value).padStart(2, '0')).join(':');
  }

  function renderMedicalCooldown() {
    const seconds = Math.max(0, Math.ceil((medicalEndTime - Date.now()) / 1000));
    const label = seconds ? formatMedicalTime(seconds) : '';
    ui.medicalFull.textContent = label;
    ui.medicalFixed.textContent = label;
    anchoredPill.querySelector('#rr-medical-compact').textContent = label;
    if (!seconds && medicalEndTime) { medicalEndTime = 0; localStorage.removeItem(MEDICAL_STORE); }
  }

  const PROFIT_STORE = 'torn-rr-daily-profit-v1';
  const profitDay = () => new Date().toISOString().slice(0, 10);
  const loadProfit = () => {
    try {
      const saved = JSON.parse(localStorage.getItem(PROFIT_STORE) || '{}');
      return saved.day === profitDay() && Number.isFinite(saved.profit) ? saved.profit : 0;
    } catch { return 0; }
  };
  const savedProfitDay = () => {
    try { return JSON.parse(localStorage.getItem(PROFIT_STORE) || '{}').day; }
    catch { return null; }
  };
  let dailyProfit = loadProfit();

  const netDailyProfit = () => dailyProfit - Math.max(0, Number(state.dailyExpenses) || 0);
  function renderDailyProfit() {
    if (profitDay() !== savedProfitDay()) dailyProfit = 0;
    const netProfit = netDailyProfit();
    const sign = netProfit > 0 ? '+' : netProfit < 0 ? '-' : '';
    const value = `${sign}$${Math.abs(netProfit).toLocaleString('en-US')}`;
    const color = netProfit > 0 ? '#7de095' : netProfit < 0 ? '#f28b8b' : '';
    if (document.activeElement !== ui.profitEditor) ui.profitEditor.value = netProfit;
    if (document.activeElement !== ui.expenseEditor) ui.expenseEditor.value = Math.max(0, Number(state.dailyExpenses) || 0);
    ui.profitFixed.textContent = value;
    anchoredPill.querySelector('#rr-profit-compact').textContent = value;
    [ui.profitEditor, ui.profitFixed, anchoredPill.querySelector('#rr-profit-compact')].forEach(element => { element.style.color = color; });
  }

  function renderForecast() {
    let wager = state.base;
    let previousLosses = 0;
    const compactMoney = amount => {
      if (!Number.isFinite(amount)) return '—';
      const suffixes = ['', 'K', 'M', 'B', 'T', 'Qa', 'Qi', 'Sx', 'Sp', 'Oc', 'No', 'Dc'];
      const absolute = Math.abs(amount);
      const tier = Math.min(Math.floor(Math.log10(absolute || 1) / 3), suffixes.length - 1);
      const scaled = absolute / Math.pow(1000, tier);
      const digits = scaled < 10 ? 2 : scaled < 100 ? 1 : 0;
      return `$${scaled.toFixed(digits).replace(/\.0+$/, '').replace(/(\.\d*[1-9])0+$/, '$1')}${suffixes[tier]}`;
    };
    const rows = [];
    for (let game = 1; game <= 25; game += 1) {
      const label = Number.isFinite(wager) && wager >= 0 ? `$${money(wager)}` : '—';
      const net = wager - previousLosses;
      const netLabel = Number.isFinite(net) ? `${net >= 0 ? '+' : '-'}$${money(Math.abs(net))}` : '—';
      const compactNet = Number.isFinite(net) ? `${net >= 0 ? '+' : '-'}${compactMoney(net)}` : '—';
      rows.push(`<div class="rr-forecast-item" title="Game ${game}: Bet ${label}; net if won ${netLabel}"><span>${game}</span><strong>${compactMoney(wager)}</strong><span class="rr-forecast-win${net < 0 ? ' is-loss' : ''}">${compactNet}</span></div>`);
      previousLosses += wager;
      wager = Math.round(wager * state.multiplier);
    }
    ui.forecastList.innerHTML = rows.join('');
  }

  function recordDailyProfit(won) {
    const pot = Number(String(document.querySelector('span[class^="count___"]')?.textContent || '').replace(/[^\d.-]/g, ''));
    const amount = Number.isFinite(pot) && pot > 0 ? Math.round(pot / 2) : currentBet();
    dailyProfit += won ? amount : -amount;
    localStorage.setItem(PROFIT_STORE, JSON.stringify({ day: profitDay(), profit: dailyProfit }));
    renderDailyProfit();
  }

  function renderTurboControls() {
    const turbo = turboMultiplier();
    [ui.turbo2, ui.turbo2Pill, anchoredPill.querySelector('#rr-anchor-turbo-2')].forEach(button => button.classList.toggle('is-active', turbo === 2));
    [ui.turbo3, ui.turbo3Pill, anchoredPill.querySelector('#rr-anchor-turbo-3')].forEach(button => button.classList.toggle('is-active', turbo === 3));
  }

  function startTurbo(multiplier) {
    state.turboMultiplier = multiplier;
    state.losses = 0;
    state.lastOutcome = 'ready';
    save();
    render();
    fillWagerWhenAvailable();
  }

  function render() {
    ui.power.textContent = state.enabled ? 'Active' : 'Inactive';
    ui.power.setAttribute('aria-pressed', String(state.enabled));
    if (document.activeElement !== ui.base) ui.base.value = state.base;
    if (document.activeElement !== ui.multiplier) ui.multiplier.value = state.multiplier;
    ui.walletAutofill.checked = Boolean(state.inactiveWalletAutofill);
    ui.ghostTradeToggle.checked = Boolean(state.ghostTradeEnabled);
    ui.quickJoinToggle.checked = Boolean(state.quickJoinEnabled);
    ui.multiShotToggle.checked = Boolean(state.hideMultiShotButtons);
    ui.forecastList.hidden = !state.forecastOpen;
    ui.forecastToggle.setAttribute('aria-expanded', String(state.forecastOpen));
    ui.forecastToggle.textContent = `Next 25 games if each game loses ${state.forecastOpen ? '▾' : '▸'}`;
    ui.settings.hidden = !state.settingsOpen;
    ui.settingsToggle.setAttribute('aria-expanded', String(state.settingsOpen));
    ui.settingsToggle.textContent = `Settings ${state.settingsOpen ? '▾' : '▸'}`;
    document.body.classList.toggle('rr-helper-hide-multishot', Boolean(state.hideMultiShotButtons));
    ui.base.disabled = false;
    ui.multiplier.disabled = false;
    ui.current.textContent = `$${money(currentBet())}`;
    ui.next.textContent = `$${money(nextBet())}`;
    ui.fixedBets.textContent = `$${money(currentBet())} | $${money(nextBet())}`;
    anchoredPill.querySelector('#rr-helper-anchor-text').textContent = `$${money(currentBet())} | $${money(nextBet())}`;
    root.classList.toggle('is-minimized', state.minimized);
    syncMinimizedAnchor();
    renderDailyProfit();
    renderForecast();
    renderTurboControls();
  }

  function applyPosition() {
    if (!state.position) return;
    root.style.left = `${state.position.left}px`;
    root.style.top = `${state.position.top}px`;
    root.style.right = 'auto';
    root.style.bottom = 'auto';
  }

  function handleResult(text) {
    const message = text.toLowerCase();
    if (message.includes('you take your winnings')) {
      recordDailyProfit(true);
      state.losses = 0;
      state.turboMultiplier = 1;
      state.lastOutcome = 'win';
      save();
      render();
      if (state.enabled) fillWagerWhenAvailable();
    } else if (message.includes('you just shot a hole in')) {
      recordDailyProfit(false);
      state.losses += 1;
      state.lastOutcome = 'loss';
      save();
      render();
      if (state.enabled) fillWagerWhenAvailable();
    }
  }

  ui.power.addEventListener('click', () => { state.enabled = !state.enabled; save(); render(); fillWagerWhenAvailable(); });
  ui.walletAutofill.addEventListener('change', () => { state.inactiveWalletAutofill = ui.walletAutofill.checked; save(); fillWagerWhenAvailable(); });
  ui.ghostTradeToggle.addEventListener('change', () => { state.ghostTradeEnabled = ui.ghostTradeToggle.checked; save(); });
  ui.quickJoinToggle.addEventListener('change', () => { state.quickJoinEnabled = ui.quickJoinToggle.checked; if (!state.quickJoinEnabled) removeJoinProxy({ cancelNative: true }); save(); });
  ui.multiShotToggle.addEventListener('change', () => { state.hideMultiShotButtons = ui.multiShotToggle.checked; save(); render(); });
  ui.turbo2.addEventListener('click', () => startTurbo(2));
  ui.turbo3.addEventListener('click', () => startTurbo(3));
  ui.turbo2Pill.addEventListener('click', () => startTurbo(2));
  ui.turbo3Pill.addEventListener('click', () => startTurbo(3));
  ui.reset.addEventListener('click', () => {
    state.losses = 0;
    state.turboMultiplier = 1;
    state.lastOutcome = 'ready';
    save();
    render();
    fillWagerWhenAvailable();
  });
  ui.base.addEventListener('input', () => { state.base = Math.max(1, Number(ui.base.value) || 1); save(); render(); fillWagerWhenAvailable(); });
  ui.multiplier.addEventListener('input', () => { state.multiplier = Math.max(1, Number(ui.multiplier.value) || 1); save(); render(); fillWagerWhenAvailable(); });
  ui.base.addEventListener('blur', render);
  ui.multiplier.addEventListener('blur', render);
  const saveProfitCorrection = () => {
    const corrected = Number(String(ui.profitEditor.value).replace(/[^\d.-]/g, ''));
    if (!Number.isFinite(corrected)) { renderDailyProfit(); return; }
    dailyProfit = Math.round(corrected + Math.max(0, Number(state.dailyExpenses) || 0));
    localStorage.setItem(PROFIT_STORE, JSON.stringify({ day: profitDay(), profit: dailyProfit }));
    renderDailyProfit();
  };
  const saveDailyExpenses = () => {
    const expenses = Number(String(ui.expenseEditor.value).replace(/[^\d.-]/g, ''));
    if (!Number.isFinite(expenses)) { renderDailyProfit(); return; }
    state.dailyExpenses = Math.max(0, Math.round(expenses));
    save();
    renderDailyProfit();
  };
  ui.profitEditor.addEventListener('blur', saveProfitCorrection);
  ui.profitEditor.addEventListener('keydown', event => { if (event.key === 'Enter') { event.preventDefault(); ui.profitEditor.blur(); } });
  ui.expenseEditor.addEventListener('blur', saveDailyExpenses);
  ui.expenseEditor.addEventListener('keydown', event => { if (event.key === 'Enter') { event.preventDefault(); ui.expenseEditor.blur(); } });
  ui.forecastToggle.addEventListener('click', () => { state.forecastOpen = !state.forecastOpen; save(); render(); });
  ui.settingsToggle.addEventListener('click', () => { state.settingsOpen = !state.settingsOpen; save(); render(); });
  root.querySelector('#rr-minimize').addEventListener('click', () => { state.minimized = true; save(); render(); });
  root.querySelector('#rr-expand').addEventListener('click', event => { if (suppressExpand) { event.preventDefault(); suppressExpand = false; return; } state.minimized = false; save(); render(); });
  anchoredPill.querySelector('#rr-anchor-expand').addEventListener('click', () => { state.minimized = false; save(); render(); });
  anchoredPill.querySelector('#rr-anchor-turbo-2').addEventListener('click', () => startTurbo(2));
  anchoredPill.querySelector('#rr-anchor-turbo-3').addEventListener('click', () => startTurbo(3));
  window.matchMedia('(max-width: 600px)').addEventListener('change', () => { if (state.minimized) syncMinimizedAnchor(); });

  let pendingStartButton = null;
  let startConfirmObserver = null;
  let joinProxyObserver = null;
  function removeJoinProxy({ cancelNative = false } = {}) {
    joinProxyObserver?.disconnect();
    joinProxyObserver = null;
    document.querySelector('#rr-helper-join-proxy')?.remove();
    if (pendingStartButton?.confirmation === 'join') {
      pendingStartButton = null;
      startConfirmObserver?.disconnect();
      startConfirmObserver = null;
    }
    const nativeJoin = [...document.querySelectorAll('button[data-type="confirm"]')]
      .find(button => button.textContent.trim().toLowerCase() === 'join');
    if (!nativeJoin) return;
    nativeJoin.style.removeProperty('visibility');
    if (cancelNative) nativeJoin.closest('[class*="confirmWrap"]')?.querySelector('button[data-type="cancel"]')?.click();
  }
  function showJoinProxy(x, y, gameId) {
    removeJoinProxy();
    const proxy = document.createElement('button');
    proxy.id = 'rr-helper-join-proxy';
    proxy.type = 'button';
    proxy.textContent = 'Join';
    proxy.dataset.rrGameId = gameId;
    proxy.style.left = `${x}px`;
    proxy.style.top = `${y}px`;
    proxy.addEventListener('click', () => {
      const nativeJoin = [...document.querySelectorAll('button[data-type="confirm"]')]
        .find(button => button.textContent.trim().toLowerCase() === 'join');
      if (nativeJoin) { proxy.remove(); nativeJoin.click(); return; }
      proxy.dataset.rrWaiting = 'true';
    });
    document.body.append(proxy);
    joinProxyObserver = new MutationObserver(() => {
      if (!document.getElementById(proxy.dataset.rrGameId)) removeJoinProxy();
    });
    joinProxyObserver.observe(document.documentElement, { childList: true, subtree: true });
  }
  function placeStartConfirmation() {
    if (!pendingStartButton) return false;
    const confirmButton = [...document.querySelectorAll('button[data-type="confirm"]')]
      .find(button => button.textContent.trim().toLowerCase() === pendingStartButton.confirmation);
    if (!confirmButton) return false;
    if (pendingStartButton.confirmation === 'join') {
      confirmButton.style.setProperty('visibility', 'hidden', 'important');
      const proxy = document.querySelector('#rr-helper-join-proxy');
      if (proxy?.dataset.rrWaiting === 'true') { proxy.remove(); confirmButton.click(); }
    } else {
      const confirmWrap = confirmButton.closest('[class*="confirmWrap"]');
      confirmButton.classList.add('rr-helper-quick-confirm');
      confirmWrap?.classList.add('rr-helper-quick-confirm');
      const confirmRect = confirmButton.getBoundingClientRect();
      const left = Math.max(0, pendingStartButton.x - confirmRect.width / 2);
      const top = Math.max(0, pendingStartButton.y - confirmRect.height / 2);
      Object.assign(confirmButton.style, {
        position: 'fixed',
        zIndex: '999999',
        left: `${left}px`,
        top: `${top}px`,
        transform: 'scale(1.5)',
        transformOrigin: 'center center'
      });
    }
    pendingStartButton = null;
    startConfirmObserver?.disconnect();
    startConfirmObserver = null;
    return true;
  }
  document.addEventListener('click', event => {
    const joinButton = event.target.closest('button[data-id][class*="submit"]');
    const startButton = event.target.closest('button[class*="submit"]');
    const confirmation = joinButton ? (state.quickJoinEnabled ? 'join' : null) : startButton && /^\s*start\s*$/i.test(startButton.textContent) ? 'yes' : null;
    if (!confirmation) return;
    if (joinButton) showJoinProxy(event.clientX, event.clientY, joinButton.dataset.id);
    pendingStartButton = { x: event.clientX, y: event.clientY, confirmation };
    if (placeStartConfirmation()) return;
    startConfirmObserver?.disconnect();
    startConfirmObserver = new MutationObserver(placeStartConfirmation);
    startConfirmObserver.observe(document.documentElement, { childList: true, subtree: true });
    const observerForThisStart = startConfirmObserver;
    setTimeout(() => {
      if (startConfirmObserver !== observerForThisStart) return;
      startConfirmObserver.disconnect();
      startConfirmObserver = null;
      pendingStartButton = null;
      const nativeJoin = [...document.querySelectorAll('button[data-type="confirm"]')]
        .find(button => button.textContent.trim().toLowerCase() === 'join');
      nativeJoin?.style.removeProperty('visibility');
      removeJoinProxy();
    }, 5000);
  }, true);
  document.addEventListener('click', event => {
    const proxy = document.querySelector('#rr-helper-join-proxy');
    if (!proxy || event.target.closest('#rr-helper-join-proxy') || event.target.closest('button[data-id][class*="submit"]')) return;
    removeJoinProxy({ cancelNative: true });
  });

  applyPosition();
  render();
  renderMedicalCooldown();
  retryWagerFill();
  installRrNavigationMenu();
  const rrNavObserver = new MutationObserver(scheduleRrNavigationMenu);
  rrNavObserver.observe(document.documentElement, { childList: true, characterData: true, attributes: true, attributeFilter: ['href'], subtree: true });
  window.addEventListener('hashchange', refreshRrNavigationAfterRouteChange);
  window.addEventListener('popstate', refreshRrNavigationAfterRouteChange);
  window.addEventListener('hashchange', retryWagerFill);
  window.addEventListener('focus', retryWagerFill);
  document.addEventListener('visibilitychange', () => { if (!document.hidden) retryWagerFill(); });
  setInterval(() => { state = load(); fillWager(); }, 300);
  setInterval(renderMedicalCooldown, 1000);
  window.addEventListener('storage', event => {
    if (event.key !== MEDICAL_STORE) return;
    medicalEndTime = Number(event.newValue) || 0;
    renderMedicalCooldown();
  });
  window.addEventListener('storage', event => {
    if (event.key !== STORAGE_KEY) return;
    state = load();
    render();
    if (state.enabled) fillWagerWhenAvailable();
  });

  const seenMessageText = new WeakMap();
  const messageSelector = '.messageWrap___WPvHT .message___uTMIk, [class*="messageWrap"] [class*="message"]';
  const inspectResultMessage = (message, processNewMessages = true) => {
    if (!message?.matches?.(messageSelector)) return;
    const text = (message.textContent || '').trim();
    if (seenMessageText.get(message) === text) return;
    seenMessageText.set(message, text);
    if (processNewMessages) handleResult(text);
  };
  const inspectResultNode = (node, processNewMessages = true) => {
    const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
    if (!element) return;
    inspectResultMessage(element.closest(messageSelector), processNewMessages);
    element.querySelectorAll?.(messageSelector).forEach(message => inspectResultMessage(message, processNewMessages));
  };
  document.querySelectorAll(messageSelector).forEach(message => inspectResultMessage(message, false));
  const resultObserver = new MutationObserver(records => records.forEach(record => {
    inspectResultNode(record.target);
    record.addedNodes.forEach(node => inspectResultNode(node));
  }));
  resultObserver.observe(document.documentElement, { childList: true, characterData: true, subtree: true });

  const dragHandles = [root.querySelector('.rr-head'), root.querySelector('.rr-pill')];
  let drag = null;
  let suppressExpand = false;
  function beginDrag(event) {
    if (event.currentTarget.classList.contains('rr-head') && event.target.closest('button')) return;
    const rect = root.getBoundingClientRect();
    drag = { offsetX: event.clientX - rect.left, offsetY: event.clientY - rect.top, startX: event.clientX, startY: event.clientY, moved: false };
    event.currentTarget.setPointerCapture(event.pointerId);
  }
  function moveDrag(event) {
    if (!drag) return;
    if (Math.abs(event.clientX - drag.startX) > 3 || Math.abs(event.clientY - drag.startY) > 3) drag.moved = true;
    const rect = root.getBoundingClientRect();
    const left = Math.max(0, Math.min(window.innerWidth - rect.width, event.clientX - drag.offsetX));
    const top = Math.max(0, Math.min(window.innerHeight - rect.height, event.clientY - drag.offsetY));
    root.style.left = `${left}px`;
    root.style.top = `${top}px`;
    root.style.right = 'auto';
    root.style.bottom = 'auto';
  }
  function endDrag() {
    if (!drag) return;
    const rect = root.getBoundingClientRect();
    state.position = { left: Math.round(rect.left), top: Math.round(rect.top) };
    save();
    suppressExpand = drag.moved;
    drag = null;
  }
  dragHandles.forEach(handle => {
    handle.addEventListener('pointerdown', beginDrag);
    handle.addEventListener('pointermove', moveDrag);
    handle.addEventListener('pointerup', endDrag);
    handle.addEventListener('pointercancel', endDrag);
  });
})();