HuskerDaddy's RR Script

HuskerDaddy's martingale wager helper for Torn Russian Roulette.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Вам потребуется установить расширение, например Tampermonkey или Violentmonkey, чтобы установить этот скрипт.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например 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);
  });
})();