Vjudge Better!

一个让vjudge更美观更便捷的脚本(顶栏约90%不透明、Element 风格边框)。提交弹窗内代码框:等宽字体 + 高度 + Tab 缩进(不加载外部编辑器,避免 CSP/加载失败导致无法输入)。兼容 Competitive Companion。

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         Vjudge Better!
// @namespace    https://github.com/1000ttank/vjudge-better
// @version      1.5.3
// @description  一个让vjudge更美观更便捷的脚本(顶栏约90%不透明、Element 风格边框)。提交弹窗内代码框:等宽字体 + 高度 + Tab 缩进(不加载外部编辑器,避免 CSP/加载失败导致无法输入)。兼容 Competitive Companion。
// @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.");
    }

    /* 题面 iframe 内:不要重复铺 bg.jpg(与父页整页背景错位叠两层);html/body 透明,只保留毛玻璃看见父页背景 */
    if (window.self !== window.top) {
        GM_addStyle(`
        html, body {
            background-color: transparent !important;
            background-image: none !important;
        }
        main, .container, .container-fluid, .container-md, .container-lg, .content, .body-content {
            background-color: transparent !important;
            background-image: none !important;
        }
        html::-webkit-scrollbar,
        body::-webkit-scrollbar {
            width: 10px;
            height: 10px;
        }
        html::-webkit-scrollbar-track,
        body::-webkit-scrollbar-track {
            background: transparent;
        }
        html::-webkit-scrollbar-thumb,
        body::-webkit-scrollbar-thumb {
            background: rgba(255, 255, 255, 0.5);
            border-radius: 5px;
        }
        html::-webkit-scrollbar-thumb:hover,
        body::-webkit-scrollbar-thumb:hover {
            background: rgba(255, 255, 255, 0.65);
        }
        html {
            scrollbar-color: rgba(255, 255, 255, 0.5) transparent;
            scrollbar-width: thin;
        }
        #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;
        }
        /* 题面页:底层铺满毛玻璃,题目内容在上层,避免 card 外围透明露底 */
        body .card {
            position: relative !important;
            isolation: isolate !important;
            background: transparent !important;
            backdrop-filter: none !important;
        }
        body .card::before {
            content: "" !important;
            position: absolute !important;
            inset: 0 !important;
            z-index: 0 !important;
            border-radius: inherit !important;
            background-color: rgba(255, 255, 255, 0.65) !important;
            backdrop-filter: blur(6px) !important;
            -webkit-backdrop-filter: blur(6px) !important;
            pointer-events: none !important;
        }
        body .card > * {
            position: relative !important;
            z-index: 1 !important;
        }
        body .card:hover::before {
            background-color: rgba(255, 255, 255, 0.85) !important;
        }
        body .card:hover {
            background-color: transparent !important;
            backdrop-filter: none !important;
        }
        .list-group-item.active,
        a.list-group-item.active,
        .list-group-item.my-contest-item.active {
            background-color: rgba(0, 123, 255, 0.75) !important;
            color: #fff !important;
            border-color: rgba(0, 123, 255, 0.75) !important;
            font-weight: 600;
        }
        .form-control {
            border: 1px solid #ccc;
        }
        .page-item.active .page-link,
        .page-item.active button.page-link,
        button.page-link[aria-current="page"],
        a.page-link[aria-current="page"] {
            background-color: rgba(0, 123, 255, 0.75) !important;
            color: #fff !important;
            border-color: rgba(0, 123, 255, 0.75) !important;
        }
        `);
        if (pageId === 3) {
            GM_addStyle("dd {background-color: rgba(255,255,255,0.7) !important; border-radius: 4px !important;}");
        }
        return;
    }

    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);
        /* 用 padding 顶开固定顶栏即可;勿 prepend 占位 nav,以免多一层 DOM 影响扩展等对 body 子节点结构的假设(如 Competitive Companion 解析 outerHTML) */
        GM_addStyle(`body { padding-top: 60px !important; box-sizing: border-box !important; }`);
    }

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

    /* VJudge 顶栏为 <nav id="top-nav" class="navbar navbar-dark bg-dark ...">,需覆盖 Bootstrap 深色主题 */
    GM_addStyle(`
    nav#top-nav.navbar,
    nav#top-nav.navbar.bg-dark,
    nav#top-nav.navbar.navbar-dark,
    #top-nav.prod {
        /* 菜单栏约 90% 不透明 + 毛玻璃 */
        background-color: rgba(255, 255, 255, 0.9) !important;
        background-image: none !important;
        --bs-navbar-color: #212529;
        --bs-navbar-hover-color: #000000;
        --bs-navbar-disabled-color: rgba(33, 37, 41, 0.58);
        --bs-navbar-active-color: #000000;
        --bs-navbar-brand-color: #212529;
        --bs-navbar-brand-hover-color: #000000;
        border-bottom: 1px solid #e4e7ed !important;
        backdrop-filter: blur(12px) saturate(180%);
        -webkit-backdrop-filter: blur(12px) saturate(180%);
        box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08) !important;
        position: fixed !important;
        top: 0;
        left: 0;
        width: 100%;
        z-index: 1000;
    }
    #header,
    #header.navbar,
    header.navbar {
        background-color: rgba(255, 255, 255, 0.9) !important;
        background-image: none !important;
        border-bottom: 1px solid #e4e7ed !important;
        backdrop-filter: blur(12px) saturate(180%);
        -webkit-backdrop-filter: blur(12px) saturate(180%);
        box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08) !important;
    }
    .navbar.navbar-dark:not(#top-nav),
    .navbar.bg-dark:not(#top-nav) {
        background-color: rgba(255, 255, 255, 0.9) !important;
        border-bottom: 1px solid #e4e7ed !important;
        backdrop-filter: blur(12px) saturate(180%);
        -webkit-backdrop-filter: blur(12px) saturate(180%);
        box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08) !important;
    }
    #top-nav .navbar-brand,
    #top-nav .navbar-brand span,
    #top-nav .nav-link,
    #top-nav .navbar-nav .nav-link,
    .navbar.navbar-dark#top-nav .nav-link,
    .navbar.navbar-dark#top-nav .navbar-brand {
        color: #212529 !important;
        font-weight: 500;
    }
    #top-nav .nav-link:hover,
    #top-nav .nav-link:focus,
    #top-nav .navbar-brand:hover {
        color: #000000 !important;
        background-color: rgba(0, 0, 0, 0.06) !important;
    }
    #top-nav .navbar-toggler {
        border-color: rgba(0, 0, 0, 0.4) !important;
    }
    #top-nav .navbar-toggler-icon {
        filter: invert(1) brightness(0.35);
    }
    /* 顶栏当前路由:仅加粗,不要蓝底 */
    #top-nav .nav-item.active > .nav-link,
    #top-nav .nav-item.active > .nav-link.active,
    #top-nav .navbar .nav-link.active {
        background-color: transparent !important;
        background-image: none !important;
        color: #212529 !important;
        font-weight: 700 !important;
    }
    #top-nav .nav-item.active > .nav-link:hover,
    #top-nav .nav-item.active > .nav-link:focus {
        background-color: rgba(0, 0, 0, 0.05) !important;
        color: #000000 !important;
    }
    .navbar a,
    .navbar .nav-link,
    .navbar-brand {
        color: #212529 !important;
        font-weight: 500;
    }
    .navbar a:hover,
    .navbar .nav-link:hover {
        color: #000000 !important;
    }
    .navbar .nav-item.active > .nav-link {
        font-weight: 700 !important;
    }
    /* 分页 / 按钮等主色高亮(非顶栏) */
    .btn.active,
    .dropdown-item.active,
    .tag.active {
        background-color: #007bff !important;
        color: #fff !important;
    }
    /* 分页当前页:Bootstrap 主色 #007bff,填充 75% 不透明(色相不变) */
    .page-item.active .page-link,
    .page-item.active button.page-link,
    button.page-link[aria-current="page"],
    a.page-link[aria-current="page"] {
        background-color: rgba(0, 123, 255, 0.75) !important;
        color: #fff !important;
        border-color: rgba(0, 123, 255, 0.75) !important;
    }
    /* 题面/页面滚动条:白色 50% 透明(整页滚动,不用固定层以免无法滚动) */
    html::-webkit-scrollbar,
    body::-webkit-scrollbar,
    #frame-description-container::-webkit-scrollbar,
    #contest_problem .tab-content::-webkit-scrollbar,
    #contest_problem::-webkit-scrollbar {
        width: 10px;
        height: 10px;
    }
    html::-webkit-scrollbar-track,
    body::-webkit-scrollbar-track,
    #frame-description-container::-webkit-scrollbar-track,
    #contest_problem .tab-content::-webkit-scrollbar-track,
    #contest_problem::-webkit-scrollbar-track {
        background: transparent;
    }
    html::-webkit-scrollbar-thumb,
    body::-webkit-scrollbar-thumb,
    #frame-description-container::-webkit-scrollbar-thumb,
    #contest_problem .tab-content::-webkit-scrollbar-thumb,
    #contest_problem::-webkit-scrollbar-thumb {
        background: rgba(255, 255, 255, 0.5);
        border-radius: 5px;
    }
    html::-webkit-scrollbar-thumb:hover,
    body::-webkit-scrollbar-thumb:hover,
    #frame-description-container::-webkit-scrollbar-thumb:hover,
    #contest_problem .tab-content::-webkit-scrollbar-thumb:hover,
    #contest_problem::-webkit-scrollbar-thumb:hover {
        background: rgba(255, 255, 255, 0.65);
    }
    html {
        scrollbar-color: rgba(255, 255, 255, 0.5) transparent;
        scrollbar-width: thin;
    }
    #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;
    }
    /* 顶栏「帮助」「语言」等下拉(须写在通用 .dropdown-menu 之后):背后强模糊,面板约 85% 不透明 */
    #top-nav .dropdown-menu,
    .navbar-nav.top-nav-secondary .dropdown-menu,
    .switch-locale-dropdown {
        background-color: rgba(255, 255, 255, 0.85) !important;
        backdrop-filter: blur(20px) saturate(180%) !important;
        -webkit-backdrop-filter: blur(20px) saturate(180%) !important;
        border: 1px solid rgba(0, 0, 0, 0.06) !important;
        box-shadow: 0 8px 24px rgba(0, 21, 41, 0.12) !important;
    }
    #top-nav .dropdown-menu:hover,
    .navbar-nav.top-nav-secondary .dropdown-menu:hover,
    .switch-locale-dropdown:hover {
        background-color: rgba(255, 255, 255, 0.9) !important;
    }
    html[data-bs-theme="dark"] #top-nav .dropdown-menu,
    html[data-bs-theme="dark"] .navbar-nav.top-nav-secondary .dropdown-menu,
    html[data-bs-theme="dark"] .switch-locale-dropdown {
        background-color: rgba(33, 37, 41, 0.85) !important;
        border-color: rgba(255, 255, 255, 0.12) !important;
    }
    /* 比赛题面:.tab-content 底层整块毛玻璃,题目列/侧栏在上层,避免外围透明圈露背景 */
    #contest_problem .tab-content {
        position: relative !important;
        isolation: isolate !important;
        background: transparent !important;
        backdrop-filter: none !important;
    }
    #contest_problem .tab-content::before {
        content: "" !important;
        position: absolute !important;
        inset: 0 !important;
        z-index: 0 !important;
        border-radius: 0.5rem !important;
        background-color: rgba(255, 255, 255, 0.65) !important;
        backdrop-filter: blur(6px) !important;
        -webkit-backdrop-filter: blur(6px) !important;
        pointer-events: none !important;
    }
    #contest_problem .tab-content:hover::before {
        background-color: rgba(255, 255, 255, 0.85) !important;
    }
    #contest_problem .tab-content > * {
        position: relative !important;
        z-index: 1 !important;
    }
    #contest_problem .tab-content .card {
        background: transparent !important;
        backdrop-filter: none !important;
    }
    #contest_problem .tab-content .card::before {
        content: none !important;
    }
    /* 单独题目页:含题面 iframe 的卡片 — 底层毛玻璃、题目在上 */
    main .card:has(#frame-description-container),
    .card:has(#frame-description-container) {
        position: relative !important;
        isolation: isolate !important;
        background: transparent !important;
        backdrop-filter: none !important;
    }
    main .card:has(#frame-description-container)::before,
    .card:has(#frame-description-container)::before {
        content: "" !important;
        position: absolute !important;
        inset: 0 !important;
        z-index: 0 !important;
        border-radius: 0.5rem !important;
        background-color: rgba(255, 255, 255, 0.65) !important;
        backdrop-filter: blur(6px) !important;
        -webkit-backdrop-filter: blur(6px) !important;
        pointer-events: none !important;
    }
    main .card:has(#frame-description-container):hover::before,
    .card:has(#frame-description-container):hover::before {
        background-color: rgba(255, 255, 255, 0.85) !important;
    }
    main .card:has(#frame-description-container) > *,
    .card:has(#frame-description-container) > * {
        position: relative !important;
        z-index: 1 !important;
    }
    /* 侧栏 list-group 选中项:主色蓝 75% 不透明,白字 */
    .list-group-item.active,
    a.list-group-item.active,
    .list-group-item.my-contest-item.active {
        background-color: rgba(0, 123, 255, 0.75) !important;
        color: #fff !important;
        border-color: rgba(0, 123, 255, 0.75) !important;
        font-weight: 600;
    }
    .form-control {
        border: 1px solid #ccc;
    }
    .body-footer {
        color: #333;
        background: transparent !important;
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
        padding: 1em;
        border-top: none !important;
        box-shadow: none !important;
    }
    @media (max-width: 768px) {
        .navbar {
            font-size: 14px;
        }
    }

    /* 全站主区默认至少一屏;比赛页取消,避免概览/状态等 Tab 下被垫出大块空白 */
    main, .container, .content, .body-content, .container-fluid {
        min-height: 100vh !important;
    }
    body:has(#contest_problem) main.container,
    body:has(#contest-tabs) main.container,
    #contest_problem,
    #contest_problem > .container,
    #contest_problem .tab-content,
    #contest_problem .tab-pane,
    #contest_status,
    #contest_overview,
    #listStatus_wrapper {
        min-height: unset !important;
    }

    /* 仅含题面+侧栏的那一行:顶对齐,避免侧栏列被 iframe 撑高后竖线/边框「跟到」页面底部 */
    #contest_problem > .row:has(#prob-right-panel) {
        align-items: flex-start !important;
    }
    /* 比赛:包住「概览/题目/…」Tab 与 .tab-content 的那一行(毛玻璃框主体),顶对齐减轻竖向被内容撑出长边线 */
    .container-md:has(#contest-tabs) > .row:has(#contest-tabs),
    .container:has(#contest-tabs) > .row:has(#contest-tabs) {
        align-items: flex-start !important;
    }
    #prob-right-panel {
        overflow-x: visible !important;
    }
    /* 题面行容器:不再做左右 25px 拉宽,仅保留无装饰边 */
    #frame-description-container {
        max-width: none !important;
        box-sizing: border-box !important;
        position: relative !important;
        background: transparent !important;
        border: none !important;
        box-shadow: none !important;
        border-radius: 0 !important;
    }
    #frame-description-container iframe[src*="problem/description"] {
        width: 100% !important;
        max-width: 100% !important;
        box-sizing: border-box !important;
        border: none !important;
        vertical-align: top !important;
    }
    #time-info,
    body #time-info.row.card {
        margin-left: -25px !important;
        margin-right: -25px !important;
        width: calc(100% + 50px) !important;
        max-width: none !important;
        box-sizing: border-box !important;
        position: relative !important;
    }
    `);

    let footerCreditDone = false;
    function appendFooterCreditOnce() {
        if (footerCreditDone) return;
        const $f = $("div.body-footer");
        if (!$f.length) return;
        footerCreditDone = true;
        $f.append(
            '<p style="text-align:center">Theme enhanced by <a href="https://github.com/1000ttank/vjudge-better" target="_blank">vjudge-better</a></p>'
        );
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", appendFooterCreditOnce);
    } else {
        appendFooterCreditOnce();
    }
    window.addEventListener("load", appendFooterCreditOnce);
    setTimeout(appendFooterCreditOnce, 400);
    setTimeout(appendFooterCreditOnce, 900);
})();

/* 比赛 Tab 内容框加宽(已取消题面左右 25px 拉宽与 .tab-content max-height) */
(function contestProblemWiden() {
    if (window.self !== window.top) {
        return;
    }
    try {
        /* 若仍需完全关闭本段布局脚本以配合外部工具,可在控制台执行:localStorage.setItem("vjudge-better-disable-layout-js","1") 后刷新 */
        if (localStorage.getItem("vjudge-better-disable-layout-js") === "1") {
            return;
        }
    } catch (e) {
        /* ignore */
    }
    const EXPAND_EACH = 25;
    const EXTRA = EXPAND_EACH * 2;
    const overflowFixed = new WeakSet();

    function liftOverflowChain(el) {
        let n = el && el.parentElement;
        for (let i = 0; i < 8 && n && n !== document.documentElement; i++) {
            if (overflowFixed.has(n)) {
                n = n.parentElement;
                continue;
            }
            const st = getComputedStyle(n);
            if (st.overflowX === "hidden" || st.overflow === "hidden" || st.overflow === "clip") {
                n.style.setProperty("overflow-x", "visible", "important");
                n.style.setProperty("overflow", "visible", "important");
                overflowFixed.add(n);
            }
            n = n.parentElement;
        }
    }

    function applyWiden(el) {
        if (!el || !el.style) return;
        el.style.setProperty("margin-left", `-${EXPAND_EACH}px`, "important");
        el.style.setProperty("margin-right", `-${EXPAND_EACH}px`, "important");
        el.style.setProperty("width", `calc(100% + ${EXTRA}px)`, "important");
        el.style.setProperty("max-width", "none", "important");
        el.style.setProperty("box-sizing", "border-box", "important");
        el.style.setProperty("position", "relative", "important");
        liftOverflowChain(el);
    }

    /** 清除此前脚本写在题面容器/iframe 上的拉宽内联样式 */
    function resetContestFrameInlineStyles() {
        const host = document.getElementById("frame-description-container");
        if (!host) return;
        ["margin-left", "margin-right", "width", "max-width"].forEach((p) => host.style.removeProperty(p));
        host.querySelectorAll("iframe").forEach((ifr) => {
            ["margin-left", "margin-right", "width", "max-width"].forEach((p) => ifr.style.removeProperty(p));
        });
        delete host.dataset.vjudgeWidenOnce;
        delete host.dataset.vjudgeWidenBase;
    }

    /**
     * 比赛主内容「框」:#contest-tabs 与 .tab-content 所在 .row,按外层 container 宽度 +50px 左右各扩 25px。
     *(脚本里 .tab-content 的毛玻璃样式形成视觉上的大框,不是时间条。)
     */
    function applyWidenContestTabShell() {
        const tabs = document.getElementById("contest-tabs");
        if (!tabs) return;
        const row = tabs.closest(".row");
        const wrap = tabs.closest(".container-md, .container-lg, .container, .container-fluid");
        if (!row || !wrap || !row.style) return;
        const w = Math.round(wrap.getBoundingClientRect().width);
        if (w <= 0) return;
        row.style.setProperty("margin-left", `-${EXPAND_EACH}px`, "important");
        row.style.setProperty("margin-right", `-${EXPAND_EACH}px`, "important");
        row.style.setProperty("width", `${w + EXTRA}px`, "important");
        row.style.setProperty("max-width", "none", "important");
        row.style.setProperty("box-sizing", "border-box", "important");
        row.style.setProperty("align-items", "flex-start", "important");
        row.dataset.vjudgeTabShellBase = String(w);
        liftOverflowChain(row);
    }

    function isDescIframe(el) {
        if (!el || el.tagName !== "IFRAME") return false;
        const src = el.getAttribute("src") || "";
        return /problem\/description/i.test(src);
    }

    /**
     * Competitive Companion 的 VirtualJudgeProblemParser 用 documentElement.outerHTML 解析,
     * 并依赖 #prob-right-panel iframe 的 src 与内联 style(如 display)。勿对该区域 iframe 写拉宽等内联样式。
     */
    function isUnderProbRightPanel(el) {
        return !!(el && el.closest && el.closest("#prob-right-panel"));
    }

    function resetIframeFillContainer(el) {
        if (!el || !el.style) return;
        el.style.setProperty("margin-left", "0", "important");
        el.style.setProperty("margin-right", "0", "important");
        el.style.setProperty("width", "100%", "important");
        el.style.setProperty("max-width", "100%", "important");
        el.style.setProperty("box-sizing", "border-box", "important");
    }

    let contestShellResizeObs;
    /** 监听比赛外层 container,同步 Tab 框宽度与题面 iframe 容器宽度 */
    function attachContestShellResizeObserver() {
        const wrap = document.querySelector(
            ".container-md:has(#contest-tabs), .container:has(#contest-tabs), .container-lg:has(#contest-tabs), .container-fluid:has(#contest-tabs)"
        );
        if (!wrap || typeof ResizeObserver === "undefined") return;
        if (contestShellResizeObs) {
            contestShellResizeObs.disconnect();
            contestShellResizeObs = null;
        }
        contestShellResizeObs = new ResizeObserver(() => {
            applyWidenContestTabShell();
        });
        contestShellResizeObs.observe(wrap);
    }

    function ensureContestMutationObserver() {
        const cr = document.getElementById("contest_problem");
        if (!cr || cr.dataset.vjudgeMoAttached === "1") return;
        cr.dataset.vjudgeMoAttached = "1";
        const mo = new MutationObserver(() => {
            if (
                document.getElementById("frame-description-container") ||
                document.getElementById("contest-tabs")
            ) {
                scan();
            }
        });
        mo.observe(cr, { childList: true, subtree: true });
    }

    function getContestTabContentEl() {
        const tabs = document.getElementById("contest-tabs");
        if (!tabs || !tabs.parentElement) return null;
        return tabs.parentElement.querySelector(".tab-content");
    }

    /** 清除旧版脚本写在 .tab-content 上的 max-height / overflow-y */
    function resetTabContentHeightLock() {
        const tc = getContestTabContentEl();
        if (tc) {
            tc.style.removeProperty("max-height");
            tc.style.removeProperty("overflow-y");
        }
    }

    function ensureTabShownListener() {
        const tabs = document.getElementById("contest-tabs");
        if (!tabs || tabs.dataset.vjudgeTabShownListener === "1") return;
        tabs.dataset.vjudgeTabShownListener = "1";
        tabs.addEventListener("shown.bs.tab", () => {
            resetTabContentHeightLock();
            resetContestFrameInlineStyles();
            setTimeout(scan, 0);
        });
    }

    function scan() {
        const contestTabs = document.getElementById("contest-tabs");
        if (contestTabs) {
            const shellWrap = contestTabs.closest(
                ".container-md, .container-lg, .container, .container-fluid"
            );
            const tw = shellWrap ? Math.round(shellWrap.getBoundingClientRect().width) : 0;
            const tabRow = contestTabs.closest(".row");
            const prevShell = tabRow && tabRow.dataset.vjudgeTabShellBase;
            if (tabRow && tw > 0 && (prevShell !== String(tw) || !tabRow.dataset.vjudgeTabShellOnce)) {
                applyWidenContestTabShell();
                tabRow.dataset.vjudgeTabShellOnce = "1";
            }
            attachContestShellResizeObserver();
            ensureTabShownListener();
            resetTabContentHeightLock();
        }

        const frameHost = document.getElementById("frame-description-container");
        if (frameHost) {
            frameHost.querySelectorAll("iframe").forEach((el) => {
                if (isDescIframe(el) && !isUnderProbRightPanel(el)) {
                    resetIframeFillContainer(el);
                }
            });
            liftOverflowChain(frameHost);
        } else {
            document.querySelectorAll("iframe").forEach((el) => {
                if (isDescIframe(el) && !isUnderProbRightPanel(el)) {
                    applyWiden(el);
                    liftOverflowChain(el);
                }
            });
        }
        ensureContestMutationObserver();
    }

    function onRouteChange() {
        resetTabContentHeightLock();
        resetContestFrameInlineStyles();
        const host = document.getElementById("frame-description-container");
        if (host) {
            delete host.dataset.vjudgeWidenOnce;
            delete host.dataset.vjudgeWidenBase;
        }
        const tabs = document.getElementById("contest-tabs");
        const tabRow = tabs && tabs.closest(".row");
        if (tabRow) {
            delete tabRow.dataset.vjudgeTabShellOnce;
            delete tabRow.dataset.vjudgeTabShellBase;
        }
        setTimeout(scan, 0);
        setTimeout(scan, 300);
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", scan);
    } else {
        scan();
    }
    window.addEventListener("load", scan);
    window.addEventListener("hashchange", onRouteChange);
    window.addEventListener("popstate", onRouteChange);

    setTimeout(scan, 400);
    /* 从旧版脚本升级时清一次题面内联拉宽 */
    setTimeout(resetContestFrameInlineStyles, 1200);
})();

/* 提交代码弹窗:强化原生 #submit-solution(不加载 Monaco:站点 CSP/AMD 易导致加载失败;先前曾隐藏 textarea 造成无法输入) */
(function vjudgeSubmitCodeArea() {
    if (window.self !== window.top) {
        return;
    }

    function clearLegacyMonacoHide(ta) {
        if (!ta || ta.dataset.vjudgeMonacoLegacyCleared === "1") {
            return;
        }
        if (ta.style.opacity === "0" || ta.getAttribute("aria-hidden") === "true") {
            ta.style.cssText = "";
            ta.removeAttribute("aria-hidden");
            const host = document.getElementById("vjudge-monaco-submit-host");
            if (host && host.parentNode) {
                host.parentNode.removeChild(host);
            }
        }
        ta.dataset.vjudgeMonacoLegacyCleared = "1";
    }

    GM_addStyle(`
    #submit-solution {
        font-family: ui-monospace, SFMono-Regular, "Cascadia Code", "Segoe UI Mono", Menlo, Monaco, Consolas, monospace !important;
        font-size: 14px !important;
        line-height: 1.5 !important;
        tab-size: 4 !important;
        -moz-tab-size: 4 !important;
        min-height: 380px !important;
        resize: vertical !important;
        background: rgba(255, 255, 255, 0.95) !important;
        color: #1e1e1e !important;
        border: 1px solid #ced4da !important;
        border-radius: 0.375rem !important;
        padding: 10px 12px !important;
        box-sizing: border-box !important;
    }
    html[data-bs-theme="dark"] #submit-solution,
    .modal[data-bs-theme="dark"] #submit-solution {
        background: rgba(30, 30, 30, 0.95) !important;
        color: #d4d4d4 !important;
        border-color: rgba(255, 255, 255, 0.2) !important;
    }
    #submit-solution:focus {
        outline: 2px solid rgba(0, 123, 255, 0.45) !important;
        outline-offset: 0 !important;
    }
    `);

    let tabBound = false;

    function insertAtCursor(ta, text) {
        const start = ta.selectionStart;
        const end = ta.selectionEnd;
        const v = ta.value;
        ta.value = v.slice(0, start) + text + v.slice(end);
        const pos = start + text.length;
        ta.selectionStart = ta.selectionEnd = pos;
    }

    function onSubmitSolutionKeydown(ev) {
        if (ev.key !== "Tab" || ev.defaultPrevented) {
            return;
        }
        const ta = ev.target;
        if (!ta || ta.id !== "submit-solution") {
            return;
        }
        ev.preventDefault();
        if (ev.shiftKey) {
            return;
        }
        insertAtCursor(ta, "    ");
    }

    function tryBindTab() {
        const ta = document.getElementById("submit-solution");
        if (ta) {
            clearLegacyMonacoHide(ta);
        }
        if (tabBound || !ta) {
            return;
        }
        tabBound = true;
        document.addEventListener("keydown", onSubmitSolutionKeydown, true);
    }

    document.addEventListener(
        "shown.bs.modal",
        (ev) => {
            if (ev.target && ev.target.querySelector && ev.target.querySelector("#submit-solution")) {
                tryBindTab();
            }
        },
        true
    );

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", tryBindTab);
    } else {
        tryBindTab();
    }
    setTimeout(tryBindTab, 500);
})();