RED Stats Since Last

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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);
  }


})();