Greasy Fork is available in English.

Vjudge Better!

一个让vjudge更美观更便捷的脚本

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Vjudge Better!
// @namespace    https://github.com/1000ttank/vjudge-better
// @version      1.2.2
// @description  一个让vjudge更美观更便捷的脚本
// @author       1000ttank
// @match        https://vjudge.net/*
// @icon         https://vjudge.net/favicon.ico
// @require      https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.6.0/jquery.min.js
// @run-at       document-body
// @license      MIT
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    const PAGE_TYPES = [
        /\/problem$/, /\/problem\/[^\/]+$/, /\/problem\/[^\/]+\/origin$/, /\/problem\/description\/[^\/]+$/,
        /\/status$/, /\/solution\/[^\/]+\/origin$/, /\/solution\/[^\/]+\/[^\/]+$/,
        /\/contest$/, /\/contest\/[^\/]+$/, /\/contest\/statistic$/,
        /\/workbook$/, /\/article\/create$/, /\/article\/[^\/]+$/,
        /\/user$/, /\/user\/[^\/]+$/,
        /\/group$/, /\/group\/[^\/]+$/,
        /\/comment$/, /\/message$/
    ];

    function detectPageType(path) {
        const cleanPath = path.replace(/\/$/, "");
        return PAGE_TYPES.findIndex(re => re.test(cleanPath));
    }

    const pageId = detectPageType(location.pathname);

    if (pageId === -1 && location.pathname !== "/") {
        console.warn("Unknown VJudge page – possibly 404.");
    }

    const pagesNeedingBg = new Set([0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
    const frostedBackground = `body {
        background: #f0f0f0 url(http://static.rqnoj.cn/images/bg.jpg) no-repeat center top fixed !important;
        background-size: cover !important;
    }`;

    if (pagesNeedingBg.has(pageId) || pageId === -1) {
        GM_addStyle(frostedBackground);
        $("body").prepend("<nav style='height: 60px'></nav>");
    }

    if (pageId === 3) {
        GM_addStyle("dd {background-color: rgba(255,255,255,0.7) !important; border-radius: 4px !important;}");
    }

    // Global UI Theme
    GM_addStyle(`
        .navbar {
        background-color: rgba(255, 255, 255, 0.75) !important;
        backdrop-filter: blur(10px);
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        z-index: 1000;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    .navbar a,
    .navbar .nav-link,
    .navbar-brand {
        color: #000 !important;
        font-weight: 500;
    }
    .navbar a:hover,
    .navbar .nav-link:hover {
        background-color: rgba(255, 255, 255, 1.0);
        color: #000 !important;
    }
    .navbar .nav-item.active > .nav-link {
        font-weight: bold !important;
    }
    ::-webkit-scrollbar {
        display: none;
    }
    #prob-ads, #img-support {
        display: none !important;
    }
    .card,
    .list-group-item,
    .btn-secondary,
    .page-link,
    .dropdown-menu,
    .modal-content,
    .form-control,
    .tab-content {
        background-color: rgba(255, 255, 255, 0.65) !important;
        backdrop-filter: blur(6px);
        border-radius: 0.5rem;
    }
    .card:hover,
    .dropdown-menu:hover {
        background-color: rgba(255, 255, 255, 0.85) !important;
    }
    .form-control {
        border: 1px solid #ccc;
    }
    .body-footer {
        color: #333;
        background: rgba(255, 255, 255, 0.85);
        padding: 1em;
        border-top: 1px solid #ddd;
    }
    .list-group-item.active,
    .page-item.active .page-link,
    .navbar .nav-link.active,
    .btn.active,
    .dropdown-item.active,
    .tag.active {
        background-color: #007bff !important;
        color: #fff !important;
    }
    @media (max-width: 768px) {
        .navbar {
            font-size: 14px;
        }
    }

    /* 新增内容区域高度设置 */
    main, .container, .content, .body-content, .container-fluid {
        min-height: 100vh !important;
    }
    `);

    // Footer credit
    $("body > div.body-footer").append(
        '<p style="text-align:center">Theme enhanced by <a href="https://github.com/1000ttank/vjudge-better" target="_blank">vjudge-better</a></p>'
    );
})();

(function () {
  'use strict';

  const defaultSettings = {
    lang: 'zh',
    bgColor: '#f0f8ff',
    bgImg: '',
    foldBlock: false,
    prettyDropdown: false,
  };

  function getSetting(key) {
    return GM_getValue(key, defaultSettings[key]);
  }

  function setSetting(key, value) {
    GM_setValue(key, value);
  }

  window.addEventListener('load', function () {
    const logoutBtn = document.querySelector('#header .logout');
    if (!logoutBtn || document.querySelector('#vjudge-better-btn')) return;

    const settingBtn = document.createElement('button');
    settingBtn.id = 'vjudge-better-btn';
    settingBtn.textContent = 'Vjudge Better! 设置';
    settingBtn.style.cssText = `
      background-color: #003366;
      color: white;
      margin-left: 12px;
      padding: 6px 12px;
      border: none;
      border-radius: 6px;
      cursor: pointer;
    `;
    logoutBtn.parentNode.insertBefore(settingBtn, logoutBtn.nextSibling);

    settingBtn.addEventListener('click', () => {
      showSettingsPanel();
    });

    applyCustomBackground();
  });

  function applyCustomBackground() {
    const color = getSetting('bgColor');
    const imgUrl = getSetting('bgImg') || '';
    document.body.style.backgroundColor = color;
    document.body.style.backgroundImage = imgUrl ? `url(${imgUrl})` : 'none';
    document.body.style.backgroundSize = 'cover';
    document.body.style.backgroundRepeat = 'no-repeat';
    document.body.style.backgroundAttachment = 'fixed';
    document.body.style.backgroundPosition = 'center top';
  }

  function showSettingsPanel() {
    const mask = document.createElement('div');
    mask.id = 'vjudge-better-mask';
    mask.style.cssText = `
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,0.3);
      z-index: 9998;
    `;
    mask.addEventListener('click', closeSettingsPanel);

    const panel = document.createElement('div');
    panel.id = 'vjudge-better-panel';
    panel.style.cssText = `
      width: 650px;
      height: 800px;
      background: #f9fbff;
      border-radius: 12px;
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      z-index: 9999;
      display: flex;
      box-shadow: 0 0 20px rgba(0,0,0,0.2);
      font-family: sans-serif;
    `;
    panel.addEventListener('click', e => e.stopPropagation());

    const closeBtn = document.createElement('div');
    closeBtn.style.cssText = `
      position: absolute;
      top: 10px;
      right: 10px;
      width: 14px;
      height: 14px;
      background: #e74c3c;
      border-radius: 50%;
      cursor: pointer;
    `;
    closeBtn.title = '关闭';
    closeBtn.addEventListener('click', closeSettingsPanel);

    const menu = document.createElement('div');
    menu.style.cssText = `
      width: 120px;
      background: #e6f0ff;
      border-right: 1px solid #ccc;
      padding-top: 40px;
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
    `;
    const contentArea = document.createElement('div');
    contentArea.style.cssText = `
      flex: 1;
      padding: 20px;
      overflow-y: auto;
    `;

    const sections = ['基本', '个性化', '翻译', '关于'];
    const menuButtons = [];

    sections.forEach((label, idx) => {
      const btn = document.createElement('div');
      btn.textContent = label;
      btn.style.cssText = `
        padding: 10px 16px;
        cursor: pointer;
        color: #003366;
        transition: background 0.2s;
      `;
      btn.addEventListener('click', () => {
        menuButtons.forEach(b => b.style.background = '');
        btn.style.background = '#d6eaff';
        renderSection(label);
      });
      if (idx === 0) {
        btn.style.background = '#d6eaff';
        renderSection(label);
      }
      menu.appendChild(btn);
      menuButtons.push(btn);
    });

    function renderSection(section) {
      contentArea.innerHTML = '';

      if (section === '基本') {
        contentArea.innerHTML = `
          <h2>基本设置</h2>
          <label>脚本语言:</label>
          <select id="langSelect">
            <option value="zh">中文</option>
            <option value="en">English</option>
          </select>
        `;
        contentArea.querySelector('#langSelect').value = getSetting('lang');
        contentArea.querySelector('#langSelect').addEventListener('change', e => {
          setSetting('lang', e.target.value);
          alert('语言设置已保存');
        });
      } else if (section === '个性化') {
        contentArea.innerHTML = `
          <h2>个性化设置</h2>
          <label>背景颜色:</label>
          <input type="color" id="bgColorPicker"><br><br>
          <label>背景图片链接:</label>
          <input type="text" id="bgImgUrl" placeholder="可为空,不填则无背景图" style="width: 100%;"><br><br>
          <label><input type="checkbox" id="foldBlock"> 启用折叠块优化</label><br>
          <label><input type="checkbox" id="prettyDropdown"> 渲染下拉框美化</label>
        `;
        const bgPicker = contentArea.querySelector('#bgColorPicker');
        bgPicker.value = getSetting('bgColor');
        bgPicker.addEventListener('input', e => {
          setSetting('bgColor', e.target.value);
          applyCustomBackground();
        });

        const bgImg = contentArea.querySelector('#bgImgUrl');
        bgImg.value = getSetting('bgImg') || '';
        bgImg.addEventListener('change', e => {
          setSetting('bgImg', e.target.value.trim());
          applyCustomBackground();
        });

        contentArea.querySelector('#foldBlock').checked = getSetting('foldBlock');
        contentArea.querySelector('#foldBlock').addEventListener('change', e => {
          setSetting('foldBlock', e.target.checked);
        });

        contentArea.querySelector('#prettyDropdown').checked = getSetting('prettyDropdown');
        contentArea.querySelector('#prettyDropdown').addEventListener('change', e => {
          setSetting('prettyDropdown', e.target.checked);
        });
      } else if (section === '翻译') {
        contentArea.innerHTML = `
          <h2>翻译设置</h2>
          <label>翻译引擎:</label>
          <select id="engineSelect">
            <option value="deepl">DeepL(免费试用)</option>
            <option value="youdao">有道翻译</option>
          </select><br><br>
          <label>目标语言:</label>
          <select id="targetLang">
            <option value="zh">中文</option>
            <option value="en">英文</option>
            <option value="ja">日文</option>
          </select>
        `;
      } else if (section === '关于') {
        contentArea.innerHTML = `
          <h2>关于</h2>
          <p>这是一个用于增强 VJudge 页面体验的油猴脚本。</p>
          <ul>
            <li>支持自定义样式</li>
            <li>优化界面布局</li>
            <li>新增翻译与渲染增强功能</li>
          </ul>
        `;
      }
    }

    function closeSettingsPanel() {
      document.getElementById('vjudge-better-panel')?.remove();
      document.getElementById('vjudge-better-mask')?.remove();
    }

    panel.appendChild(closeBtn);
    panel.appendChild(menu);
    panel.appendChild(contentArea);
    document.body.appendChild(mask);
    document.body.appendChild(panel);
  }
})();