RED Stats Since Last

Shows how your Upload, Download and Ratio on RED/OPS have changed since your last visit. Records stats history.

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         RED Stats Since Last
// @version      2.1.3
// @description  Shows how your Upload, Download and Ratio on RED/OPS have changed since your last visit. Records stats history.
// @author       Lancer07
// @match        https://redacted.sh/*
// @match        https://orpheus.network/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @namespace    https://greasyfork.org/users/1535511
// @license      All Rights Reserved
// ==/UserScript==

(function () {
  'use strict';

  // =================== Storage Layer ===================
  const SITE = location.hostname.includes('redacted')
    ? 'RED'
    : location.hostname.includes('orpheus')
      ? 'OPS'
      : 'UNKNOWN';

  const STORAGE_KEY = `statsSinceLast_${SITE}`;

    // Buffer factor varies by site
    const BUFFER_FACTOR = {
        RED: 0.65,
        OPS: 0.6,
    }[SITE] ?? 0.65; // Default to 0.65 for unknown sites

    const Storage = {
      load() {
        const raw = GM_getValue(STORAGE_KEY);

        const defaults = {
          schemaVersion: 2,
          settings: {
            resetAfterDays: 10,
            showNoChange: true,
            highlight: true,

            // Display group
            showUpChange: true,
            showDownChange: true,
            showRatioChange: true,
            showBufferValue: false,
            showBufferChange: false,
            hideUnreachable: false,
            showGearOnProfileOnly: false,
            lastTab: 'settings', // or 'history'

          },
          baseline: null,
          lastShown: { up: 0, down: 0, ratio: 0, buffer: 0 },
          history: [],
          joined: null
        };

        // First run / no data
        if (!raw || typeof raw !== 'object') {
          return structuredClone(defaults);
        }

        let data = {
          ...defaults,
          ...raw,
          settings: { ...defaults.settings, ...(raw.settings || {}) },
          history: Array.isArray(raw.history) ? raw.history : []
        };

        // ===============================
        // 🔥 OPS history data clear (only once)
        // schemaVersion null or < 2
        // ===============================
        if (SITE === 'OPS' && (!raw.schemaVersion || raw.schemaVersion < 2)) {
          console.warn('[SSL] Clearing broken OPS history (unit bug)');
          data.history = [];
          data.baseline = null;
        }

        // ===============================
        // ✅ Mark as newest schema
        // ===============================
        data.schemaVersion = 2;

        return data;
      },

      save(data) {
        GM_setValue(STORAGE_KEY, data);
      }
    };

  // =================== Utils ===================
  // Convert bytes to human-readable format
  function renderStats(bytes) {
    const units = ['B','KB','MB','GB','TB','PB'];
    let i = 0;
    let value = bytes;
    while(Math.abs(value) >= 1024 && i < units.length - 1) {
      value /= 1024;
      i++;
    }
    return `${Number(value.toFixed(2))} ${units[i]}`;
  }

  // Parse text like "699.30 GB" to bytes
    function parseStats(text) {
      text = text.replace(/,/g, '');
      const match = text.match(/([\d.]+)\s*(KiB|MiB|GiB|TiB|PiB|KB|MB|GB|TB|PB)?/i);
      if (!match) return 0;

      let value = parseFloat(match[1]);
      const unit = (match[2] || '').toUpperCase();

      const map = {
        B: 1,
        KB: 2 ** 10,
        KIB: 2 ** 10,
        MB: 2 ** 20,
        MIB: 2 ** 20,
        GB: 2 ** 30,
        GIB: 2 ** 30,
        TB: 2 ** 40,
        TIB: 2 ** 40,
        PB: 2 ** 50,
        PIB: 2 ** 50
      };

      return Number.isFinite(value)
        ? value * (map[unit] || 1)
        : 0;
    }

    // Get current stats from page
    function getCurrentStats() {
        const upSpan = document.querySelector('#stats_seeding span.stat');
        const downSpan = document.querySelector('#stats_leeching span.stat');
        const ratioSpan = document.querySelector('#stats_ratio span.stat');

        // If any element is missing, the page hasn't loaded stats yet
        if (!upSpan || !downSpan || !ratioSpan) return null;

        return {
            up: parseStats(upSpan.textContent),
            down: parseStats(downSpan.textContent),
            ratio: parseFloat(ratioSpan.textContent) || 0,
            time: Date.now()
        };
    }

  // Determine if baseline should reset
  function shouldReset(baseline, days) {
    if(!baseline) return true;
    return Date.now() - baseline.time > days * 86400000;
  }

  // Record a daily snapshot, only once per day
    function recordDailyHistory(data, current) {
        const now = new Date();
        const today = `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2,'0')}-${now.getDate().toString().padStart(2,'0')}`;
        const last = data.history[data.history.length - 1];

        // No history at all
        if (!last) {
            data.history.push({ ...current, date: today });
            return;
        }

        // Global monotonic check (against last record)
        const safeUp = current.up >= last.up ? current.up : last.up;
        const safeDown = current.down >= last.down ? current.down : last.down;
        const safeRatio = Number.isFinite(current.ratio) ? current.ratio : last.ratio;

        // Same day → overwrite
        if (last.date === today) {
            last.up = safeUp;
            last.down = safeDown;
            last.ratio = safeRatio;
            last.time = current.time;
            return;
        }

        // New day → append
        data.history.push({
            up: safeUp,
            down: safeDown,
            ratio: safeRatio,
            time: current.time,
            date: today
        });

        // Only 365 days of history records are saved.
        if (data.history.length > 365) {
            data.history.shift();
        }
    }

  // Calculate differences between current stats and a historical entry
  function getHistoryDiff(current, historyEntry){
    if(!historyEntry) return null;
    return {
      upChange: current.up - historyEntry.up,
      downChange: current.down - historyEntry.down,
      ratioChange: Math.round((current.ratio - historyEntry.ratio) * 100) / 100,
      bufferChange: Math.round((current.up/BUFFER_FACTOR - current.down) - (historyEntry.up/BUFFER_FACTOR - historyEntry.down)),
      upValue: historyEntry.up,
      downValue: historyEntry.down,
      ratioValue: historyEntry.ratio
    };
  }

    function getMyUserId() {
      const a = document.querySelector('#userinfo_username a.username');
      if (!a) return null;
      const m = a.href.match(/id=(\d+)/);
      return m ? m[1] : null;
    }

    async function fetchJoinedFromAPI(uid) {
      try {
        const res = await fetch(`/ajax.php?action=user&id=${uid}`, {
          credentials: 'same-origin'
        });
        const json = await res.json();

        const joined = json?.response?.stats?.joinedDate;
        if (!joined) return null;

        return {
          time: new Date(joined.replace(' ', 'T') + 'Z').getTime(),
          source: 'api'
        };
      } catch {
        return null;
      }
    }

    function getAverageUploadPerDay(currentUp, joinedTime) {
      const days = (Date.now() - joinedTime) / 86400000;
      if (days <= 0) return 0;
      return currentUp / days;
    }

  // =================== UI Styles ===================
  const style = document.createElement('style');
  style.textContent = `
    .ssl-highlight { color:#2ecc71; font-weight:bold; }
    #ssl-modal { position:fixed; inset:0; background:rgba(0,0,0,0.7); display:flex; align-items:center; justify-content:center; z-index:9999; }
    #ssl-card { background:#1e1e1e; border:2px solid #333; color:#eee; padding:20px; width:300px; border-radius:12px; box-shadow:0 12px 40px rgba(0,0,0,0.7); font-size:14px; position:relative; font-family:sans-serif; }
    #ssl-card h2 { margin:0 0 1px; font-size:18px; text-align:center; }
    #ssl-card label { display:flex; justify-content:space-between; align-items:center; margin:5px 0; }
    #ssl-card input[type="number"], #ssl-card select { width:100px; padding:3px 6px; border-radius:4px; border:1px solid #555; background:#222; color:#eee; }
    #ssl-card button { margin-top:8px; width:100%; padding:8px; border-radius:6px; border:none; cursor:pointer; font-weight:bold; }
    #ssl-card button.primary {
      background-color: initial;
      background-image: linear-gradient(-180deg, #00D775, #00BD68);
      border-radius: 5px;
      box-shadow: rgba(0, 0, 0, 0.1) 0 2px 4px;
      color: #FFFFFF;
      cursor: pointer;
      display: inline-block;
      font-family: Inter,-apple-system,system-ui,Roboto,"Helvetica Neue",Arial,sans-serif;
      height: 30px;
      line-height: 30px;
      outline: 0;
      overflow: hidden;
      padding: 0 20px;
      pointer-events: auto;
      position: relative;
      text-align: center;
      touch-action: manipulation;
      user-select: none;
      -webkit-user-select: none;
      vertical-align: top;
      white-space: nowrap;
      width: 100%;
      z-index: 9;
      border: 0;
    }

    #ssl-card button.primary:hover {
      box-shadow: 0 0.5em 1.5em -0.5em #14a73e98;
    }

    #ssl-card button.primary:active {
      box-shadow: 0 0.3em 1em -0.5em #14a73e98;
    }

    #ssl-card button.secondary { background:#444; color:#eee;}

    #ssl-card button.panelswitch1 {
        background: linear-gradient(to bottom right, #EF4765, #FF9A5A);
        border: 0;
        border-radius: 12px;
        color: #FFFFFF;
        cursor: pointer;
        display: inline-block;
        font-family: -apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
        font-size: 12px;
        font-weight: 500;
        line-height: 2.5;
        outline: transparent;
        padding: 0 1rem;
        text-align: center;
        text-decoration: none;
        transition: box-shadow .2s ease-in-out;
        user-select: none;
        -webkit-user-select: none;
        touch-action: manipulation;
        white-space: nowrap;
    }
    #ssl-card button.panelswitch1:hover {
      box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.5), -.125rem -.125rem 1rem rgba(239, 71, 101, 0.5), .125rem .125rem 1rem rgba(255, 154, 90, 0.5);
    }

    #ssl-card button.panelswitch1:active {
      box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.5), -.125rem -.125rem 1rem rgba(239, 71, 101, 0.5), .125rem .125rem 1rem rgba(255, 154, 90, 0.5);
    }

    #ssl-card button.panelswitch2 {
        background: linear-gradient(to bottom right, #444, #444);
        border: 0;
        border-radius: 12px;
        color: #FFFFFF;
        cursor: pointer;
        display: inline-block;
        font-family: -apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
        font-size: 12px;
        font-weight: 500;
        line-height: 2.5;
        outline: transparent;
        padding: 0 1rem;
        text-align: center;
        text-decoration: none;
        transition: box-shadow .2s ease-in-out;
        user-select: none;
        -webkit-user-select: none;
        touch-action: manipulation;
        white-space: nowrap;
    }
    #ssl-card button.panelswitch2:hover {
      box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.5), -.125rem -.125rem 1rem rgba(239, 71, 101, 0.5), .125rem .125rem 1rem rgba(255, 154, 90, 0.5);
    }

    #ssl-card button.panelswitch2:active {
      box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.5), -.125rem -.125rem 1rem rgba(239, 71, 101, 0.5), .125rem .125rem 1rem rgba(255, 154, 90, 0.5);
    }

    #ssl-gear { cursor:pointer; margin-left:6px; opacity:0.7; font-size:16px; vertical-align:middle; }
    #ssl-gear:hover { opacity:1; }

    #ssl-history-output { margin-top:10px; font-size:0.95em; overflow-y:auto; padding:14px; border-radius:8px; background:#2a2a2a; }

    .ssl-section { margin-bottom:1px; padding:8px; border-radius:6px; background:#292929; }
    .ssl-section-title { font-weight:bold; margin-bottom:2px; font-size:12px; color:#eee; border-bottom:1px solid #444; padding-bottom:2px; }

    .ssl-row { display:flex; align-items:baseline; gap:6px; margin:3px 0; }
    .ssl-row span.label { text-align:right; width:40px; flex-shrink:0; color:#bbb; }
    .ssl-row span.value { text-align:left; min-width:auto; font-weight:bold; }
    .ssl-change-positive { color:#2ecc71; }
    .ssl-change-negative { color:#e74c3c; }
    #ssl-tabs {display:flex;justify-content:space-between;gap:8px;margin-bottom:6px;}
    #ssl-tabs button {width:48%;padding:6px;}
    #ssl-close-x {position: absolute;top: 10px;right: 10px;width: 25px;height: 25px;background: #e74c3c;color: #fff;font-size: 20px;font-weight: bold;text-align: center;line-height: 25px;cursor: pointer;border-radius: 3px;}
    #ssl-close-x:hover {background: #ff6b5c;}

  `;
  document.head.appendChild(style);

  // =================== Settings Modal ===================
  function openSettings() {
    if(document.getElementById('ssl-modal')) return;
    const data = Storage.load();
    const modal = document.createElement('div');
    modal.id = 'ssl-modal';

    // Build options for dates only for existing history
    let options = '<option value="">Select date</option>';
    data.history.forEach(h => options += `<option value="${h.date}">${h.date}</option>`);

    modal.innerHTML = `
      <div id="ssl-card">
        <h2>RED Stats Since Last</h2>
        <span id="ssl-close-x">×</span>

        <!-- Tabs -->
        <div id="ssl-tabs">
          <button id="ssl-tab-btn-settings" class="panelswitch1">Settings</button>
          <button id="ssl-tab-btn-history" class="panelswitch2">History</button>
        </div>

        <!-- ================= Settings Tab ================= -->
        <div id="ssl-tab-settings">
          <label>Reset after (days)
            <input id="ssl-days" type="number" value="${data.settings.resetAfterDays}">
          </label>

          <label>Show when no change
            <input id="ssl-nochange" type="checkbox" ${data.settings.showNoChange ? 'checked' : ''}>
          </label>

          <label>Highlight changes
            <input id="ssl-highlight" type="checkbox" ${data.settings.highlight ? 'checked' : ''}>
          </label>

          <label>Only show setting icon on profile
            <input type="checkbox" id="ssl-gear-profile" ${data.settings.showGearOnProfileOnly ? 'checked' : ''}>
          </label>

        <div class="ssl-section">
          <div class="ssl-section-title">Display</div>

          <label>Show change: Upload
            <input type="checkbox" id="ssl-show-up" ${data.settings.showUpChange ? 'checked' : ''}>
          </label>

          <label>Show change: Download
            <input type="checkbox" id="ssl-show-down" ${data.settings.showDownChange ? 'checked' : ''}>
          </label>

          <label>Show change: Ratio
            <input type="checkbox" id="ssl-show-ratio" ${data.settings.showRatioChange ? 'checked' : ''}>
          </label>

          <label>✚Buffer: Show value
            <input type="checkbox" id="ssl-show-buffer-value" ${data.settings.showBufferValue ? 'checked' : ''}>
          </label>

          <label>✚Buffer: Show change
            <input type="checkbox" id="ssl-show-buffer-change" ${data.settings.showBufferChange ? 'checked' : ''}>
          </label>

          ${SITE === 'RED' ? `
          <label>Hide "Unreachable"
            <input type="checkbox" id="ssl-hide-unreachable" ${data.settings.hideUnreachable ? 'checked' : ''}>
          </label>` : ''}
        </div>

          <button class="primary" id="ssl-save">Save</button>
          <button class="secondary" id="ssl-reset">Baseline Reset</button>
        </div>

        <!-- ================= History Tab ================= -->
        <div id="ssl-tab-history" style="display:none;">
          <label>View history
            <select id="ssl-history-select">${options}</select>
          </label>

          <button class="secondary" id="ssl-delete-day" style="margin-top:6px;">
            Delete this record
          </button>

          <div id="ssl-average-upload"
               style="margin:10px 0; text-align:center; font-weight:bold; color:#ddd;">
          </div>

          <div id="ssl-history-output"></div>
        </div>
    `;

    document.body.appendChild(modal);

    const bufferValueCheckbox = modal.querySelector('#ssl-show-buffer-value');
    const bufferChangeCheckbox = modal.querySelector('#ssl-show-buffer-change');

    function syncBufferUI() {
      if (!bufferValueCheckbox.checked) {
        bufferChangeCheckbox.checked = false;
        bufferChangeCheckbox.disabled = true;
        bufferChangeCheckbox.parentElement.style.opacity = '0.5';
      } else {
        bufferChangeCheckbox.disabled = false;
        bufferChangeCheckbox.parentElement.style.opacity = '1';
      }
    }

    bufferValueCheckbox.addEventListener('change', syncBufferUI);

    // run once when modal opens
    syncBufferUI();

      const tabSettingsBtn = modal.querySelector('#ssl-tab-btn-settings');
      const tabHistoryBtn = modal.querySelector('#ssl-tab-btn-history');
      const tabSettings = modal.querySelector('#ssl-tab-settings');
      const tabHistory = modal.querySelector('#ssl-tab-history');

      // Default: show settings
      tabSettingsBtn.onclick = () => {
          tabSettings.style.display = 'block';
          tabHistory.style.display = 'none';
          tabSettingsBtn.className = 'panelswitch1';
          tabHistoryBtn.className = 'panelswitch2';
          data.settings.lastTab = 'settings';
          Storage.save(data);
      };

      tabHistoryBtn.onclick = () => {
          tabSettings.style.display = 'none';
          tabHistory.style.display = 'block';
          tabSettingsBtn.className = 'panelswitch2';
          tabHistoryBtn.className = 'panelswitch1';
          data.settings.lastTab = 'history';
          Storage.save(data);


      };


      // Restore last opened tab
    if (data.settings.lastTab === 'history') {
      document.getElementById('ssl-tab-btn-history').click();
    } else {
      document.getElementById('ssl-tab-btn-settings').click();
    }

    const avgBox = modal.querySelector('#ssl-average-upload');

    if (data.joined && RAW_STATS) {
      const avgPerDay = getAverageUploadPerDay(RAW_STATS.up, data.joined.time);
      const avgPerMonth = avgPerDay * 30;

      avgBox.textContent = `Average Upload: ${renderStats(avgPerMonth)} per month`;
    } else {
      avgBox.textContent = 'Average Upload: N/A';
    }

    modal.querySelector('#ssl-delete-day').onclick = () => {
      const date = select.value;
      if (!date) {
        alert('Please select a date first.');
        return;
      }

      if (!confirm(`Delete history for ${date}?`)) return;

      data.history = data.history.filter(h => h.date !== date);
      Storage.save(data);

      select.querySelector(`option[value="${date}"]`)?.remove();
      select.value = '';
      output.textContent = 'Deleted.';
    };


    const current = RAW_STATS;
    if (!current) {
      output.textContent = 'Stats not ready yet. Please refresh the page.';
      return;
    }
    const output = modal.querySelector('#ssl-history-output');
    const select = modal.querySelector('#ssl-history-select');

    // When a history date is selected, show debug info and totals
    select.onchange = () => {
      const entry = data.history.find(h => h.date === select.value);
      if (!entry) {
          output.textContent = 'No data.';
          return;
      }

  const currentUp = current.up;
  const currentDown = current.down;
  const currentRatio = current.ratio;
  const currentBuffer = current.up / BUFFER_FACTOR - current.down;

  const histUp = entry.up;
  const histDown = entry.down;
  const histRatio = entry.ratio;
  const histBuffer = histUp / BUFFER_FACTOR - histDown;

  const upChange = currentUp - histUp;
  const downChange = currentDown - histDown;
  const ratioChange = Math.round((currentRatio - histRatio) * 100) / 100;
  const bufferChange = Math.round(currentBuffer - histBuffer);

  function formatChange(value) {
      const sign = value > 0 ? '+' : value < 0 ? '-' : '';
      return `${sign}${renderStats(Math.abs(value))}`;
  }

  output.innerHTML = `
    <div class="ssl-section">
      <div class="ssl-section-title">Stats on ${select.value}</div>
      <div class="ssl-row"><span class="label">Up:</span><span class="value">${renderStats(histUp)}</span></div>
      <div class="ssl-row"><span class="label">Down:</span><span class="value">${renderStats(histDown)}</span></div>
      <div class="ssl-row"><span class="label">Ratio:</span><span class="value">${histRatio}</span></div>
      <div class="ssl-row"><span class="label">Buffer:</span><span class="value">${renderStats(histBuffer)}</span></div>
    </div>

    <div class="ssl-section">
      <div class="ssl-section-title">Change since ${select.value}</div>
      <div class="ssl-row"><span class="label">Up:</span><span class="value ${upChange > 0 ? 'ssl-change-positive' : upChange < 0 ? 'ssl-change-negative' : ''}">${formatChange(upChange)}</span></div>
      <div class="ssl-row"><span class="label">Down:</span><span class="value ${downChange > 0 ? 'ssl-change-positive' : downChange < 0 ? 'ssl-change-negative' : ''}">${formatChange(downChange)}</span></div>
      <div class="ssl-row"><span class="label">Ratio:</span><span class="value ${ratioChange > 0 ? 'ssl-change-positive' : ratioChange < 0 ? 'ssl-change-negative' : ''}">${ratioChange>=0?'+':''}${ratioChange}</span></div>
      <div class="ssl-row"><span class="label">Buffer:</span><span class="value ${bufferChange > 0 ? 'ssl-change-positive' : bufferChange < 0 ? 'ssl-change-negative' : ''}">${formatChange(bufferChange)}</span></div>
    </div>
  `;
};

    // Auto select ~30 days ago entry when opening History tab
    if (data.history.length > 0) {

      const now = Date.now();
      const targetTime = now - 30 * 86400000;

      const withTime = data.history.map(h => ({
        ...h,
        time: h.time || new Date(h.date + 'T00:00:00Z').getTime()
      }));

      // For new user less than 30 days, select the earliest
      const earliestTime = withTime[0].time;
      if (now - earliestTime < 30 * 86400000) {
        select.value = withTime[0].date;
        select.dispatchEvent(new Event('change'));
      } else {

        // Find the record cloest to targetTime
        let closest = withTime[0];
        let minDiff = Math.abs(withTime[0].time - targetTime);

        for (const h of withTime) {
          const diff = Math.abs(h.time - targetTime);
          if (diff < minDiff) {
            minDiff = diff;
            closest = h;
          }
        }

        select.value = closest.date;
        select.dispatchEvent(new Event('change'));
      }
    }

    modal.querySelector('#ssl-close-x').onclick = () => modal.remove();
    modal.querySelector('#ssl-reset').onclick = () => {
      data.baseline = null;
      Storage.save(data);
      alert('Baseline reset.');
    };
    modal.querySelector('#ssl-save').onclick = () => {
      data.settings.resetAfterDays = parseInt(modal.querySelector('#ssl-days').value,10) || 1;
      data.settings.showNoChange = modal.querySelector('#ssl-nochange').checked;

      data.settings.highlight = modal.querySelector('#ssl-highlight').checked;
      data.settings.showGearOnProfileOnly = modal.querySelector('#ssl-gear-profile').checked;
      data.settings.showUpChange = modal.querySelector('#ssl-show-up').checked;
      data.settings.showDownChange = modal.querySelector('#ssl-show-down').checked;
      data.settings.showRatioChange = modal.querySelector('#ssl-show-ratio').checked;
      const hideUnreachableCheckbox = modal.querySelector('#ssl-hide-unreachable');
      if (hideUnreachableCheckbox) {
        data.settings.hideUnreachable = hideUnreachableCheckbox.checked;
      }

      data.settings.showBufferValue = modal.querySelector('#ssl-show-buffer-value').checked;
      data.settings.showBufferChange = modal.querySelector('#ssl-show-buffer-change').checked;
      // Buffer change depends on Buffer value
      if (!data.settings.showBufferValue) {
        data.settings.showBufferChange = false;
      }

      Storage.save(data);
      modal.remove();
    };
  }

  const SITE_LABEL = SITE === 'RED' ? 'RED' : SITE === 'OPS' ? 'OPS' : 'PT';

  GM_registerMenuCommand(
    `${SITE_LABEL} – Settings`,
    openSettings
  );

  GM_registerMenuCommand(
    `${SITE_LABEL} – Clear ALL data`,
    () => {
      GM_setValue(STORAGE_KEY, null);
      alert(`${SITE_LABEL} stats data cleared.`);
    }
  );

  let RAW_STATS = null;
  // =================== Main Logic ===================
  const data = Storage.load();
  console.log('[SSL] baseline:', JSON.stringify(data.baseline, null, 2));

    const isProfilePage =
      location.pathname === '/user.php' && location.search.includes('id=');

    (async () => {
      if (!data.joined) {
        const uid = getMyUserId();
        if (uid) {
          const apiJoined = await fetchJoinedFromAPI(uid);
          if (apiJoined) {
            data.joined = apiJoined;
            Storage.save(data);
            return;
          }
        }
      }
    })();


  const current = getCurrentStats();
  if(!current) return;
  RAW_STATS = {
    up: current.up,
    down: current.down,
    ratio: current.ratio,
    time: current.time
  };


  recordDailyHistory(data, current);

  if(shouldReset(data.baseline, data.settings.resetAfterDays)) {
    data.baseline = current;
    Storage.save(data);
    return;
  }

  const diff = {
    up: current.up - data.baseline.up,
    down: current.down - data.baseline.down,
    ratio: Math.round((current.ratio - data.baseline.ratio) * 100) / 100,
    buffer: Math.round((current.up / BUFFER_FACTOR - current.down) - (data.baseline.up / BUFFER_FACTOR - data.baseline.down))
  };

  const spans = document.querySelectorAll('#userinfo_stats span');
  const highlights = {
    up: diff.up !== data.lastShown.up && diff.up !== 0,
    down: diff.down !== data.lastShown.down && diff.down !== 0,
    ratio: diff.ratio !== data.lastShown.ratio && diff.ratio !== 0,
    buffer: diff.buffer !== data.lastShown.buffer && diff.buffer !== 0
  };

  function append(span, text, highlight) {
    const s = document.createElement('span');
    s.textContent = ` (${text})`;
    if(highlight && data.settings.highlight) s.className='ssl-highlight';
    span.appendChild(s);
  }

  if(data.settings.showUpChange &&
     (diff.up !== 0 || data.settings.showNoChange))
    append(spans[0], renderStats(diff.up), highlights.up);

  if(data.settings.showDownChange &&
     (diff.down !== 0 || data.settings.showNoChange))
    append(spans[1], renderStats(diff.down), highlights.down);

  if(data.settings.showRatioChange &&
     (diff.ratio !== 0 || data.settings.showNoChange))
    append(spans[2], diff.ratio, highlights.ratio);

  if(data.settings.showBufferValue) {
    const li = document.createElement('li');
    li.id = 'stats_buffer';
    li.innerHTML = `Buffer: <span>${renderStats(current.up / BUFFER_FACTOR - current.down)}</span>`;
    const parent = document.getElementById('stats_ratio')?.parentNode;
    if(parent) parent.insertBefore(li, document.getElementById('stats_ratio'));

    if(data.settings.showBufferChange &&
       (diff.buffer !== 0 || data.settings.showNoChange)) {

      const span = document.createElement('span');
      span.textContent = ` (${renderStats(diff.buffer)})`;

      if(data.settings.highlight && highlights.buffer)
        span.className = 'ssl-highlight';

      li.querySelector('span').appendChild(span);
    }
  }

  data.lastShown = diff;
  Storage.save(data);

    console.log('[SSL joined debug]', data.joined);
    if (data.joined) {
        console.log('Joined date (UTC):', new Date(data.joined.time).toISOString());
    }

    // Hide "Unreachable" only on RED
    if (SITE === 'RED' && data.settings.hideUnreachable) {
      const unreachable = document.querySelector('#closed_ports');
      if (unreachable) unreachable.remove();
    }

  // =================== Inject Gear Button ===================
  const gear = document.createElement('span');
  gear.id = 'ssl-gear';
  gear.textContent = '⚙';
  gear.title = 'RED Stats Since Last Settings';
  gear.onclick = openSettings;
  if(!data.settings.showGearOnProfileOnly || isProfilePage) {
    document.getElementById('userinfo_stats').appendChild(gear);
  }


})();