// ==UserScript==
// @name ZZULIOJ iOS Style Modernizer
// @namespace https://acm.zzuli.edu.cn/
// @version 0.1.6
// @description 将 ZZULIOJ 界面美化为类 iOS 扁平圆角半透明风格,支持暗黑模式与快速切换
// @author ShiYi
// @match *://acm.zzuli.edu.cn/*
// @match *://acm.zzuli.edu.cn/*?*
// @run-at document-end
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const LS_KEY_ENABLE = 'zzuli_ios_theme_enabled';
const LS_KEY_DARK = 'zzuli_ios_theme_dark';
const LS_KEY_AUTO = 'zzuli_ios_theme_auto';
const STATE = {
enabled: localStorage.getItem(LS_KEY_ENABLE) !== 'false',
dark: localStorage.getItem(LS_KEY_DARK) === 'true',
auto: localStorage.getItem(LS_KEY_AUTO) !== 'false',
};
// 如果跟随系统,则用 matchMedia
const mqDark = window.matchMedia('(prefers-color-scheme: dark)');
if (STATE.auto) {
STATE.dark = mqDark.matches;
}
function saveState() {
localStorage.setItem(LS_KEY_ENABLE, STATE.enabled);
localStorage.setItem(LS_KEY_DARK, STATE.dark);
localStorage.setItem(LS_KEY_AUTO, STATE.auto);
applyTheme();
}
function toggleEnabled() {
STATE.enabled = !STATE.enabled;
saveState();
}
function toggleDark() {
STATE.auto = false;
STATE.dark = !STATE.dark;
saveState();
}
function toggleAuto() {
STATE.auto = !STATE.auto;
if (STATE.auto) {
STATE.dark = mqDark.matches;
}
saveState();
}
mqDark.addEventListener('change', () => {
if (STATE.auto) {
STATE.dark = mqDark.matches;
saveState();
}
});
// 创建样式
const styleTag = document.createElement('style');
styleTag.id = 'zzuli-ios-theme-style';
document.documentElement.appendChild(styleTag);
const baseCSS = `
:root {
--ios-font-stack: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", "PingFang SC", "Helvetica Neue", Arial, sans-serif;
--ios-radius-s: 6px;
--ios-radius: 12px;
--ios-radius-l: 18px;
--ios-border-separator-light: rgba(0,0,0,0.08);
--ios-border-separator-dark: rgba(255,255,255,0.15);
--ios-accent: #0A84FF;
--ios-accent-rgb: 10,132,255;
--ios-danger: #FF3B30;
--ios-bg-light: #f5f6f7;
--ios-bg-dark: #1c1c1e;
--ios-card-light: rgba(255,255,255,0.72);
--ios-card-dark: rgba(44,44,46,0.72);
--ios-text-light: #1c1c1e;
--ios-text-dark: #f5f5f7;
--ios-muted-light: #6e6e73;
--ios-muted-dark: #9d9da1;
--ios-code-bg-light: #f2f2f7;
--ios-code-bg-dark: #2c2c2e;
--ios-shadow-light: 0 2px 6px rgba(0,0,0,0.08), 0 8px 24px -8px rgba(0,0,0,0.12);
--ios-shadow-dark: 0 2px 6px rgba(0,0,0,0.6), 0 8px 24px -8px rgba(0,0,0,0.5);
--ios-transition: 150ms cubic-bezier(.4,.2,.2,1);
}
html.zzuli-ios-theme {
font-family: var(--ios-font-stack);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
html.zzuli-ios-theme body {
background: var(--zzuli-bg) !important;
color: var(--zzuli-text) !important;
transition: background-color var(--ios-transition), color var(--ios-transition);
}
html.zzuli-ios-theme.zzuli-dark {
--zzuli-bg: var(--ios-bg-dark);
--zzuli-text: var(--ios-text-dark);
--zzuli-muted: var(--ios-muted-dark);
--zzuli-card: var(--ios-card-dark);
--zzuli-separator: var(--ios-border-separator-dark);
--zzuli-code-bg: var(--ios-code-bg-dark);
--zzuli-shadow: var(--ios-shadow-dark);
}
html.zzuli-ios-theme:not(.zzuli-dark) {
--zzuli-bg: var(--ios-bg-light);
--zzuli-text: var(--ios-text-light);
--zzuli-muted: var(--ios-muted-light);
--zzuli-card: var(--ios-card-light);
--zzuli-separator: var(--ios-border-separator-light);
--zzuli-code-bg: var(--ios-code-bg-light);
--zzuli-shadow: var(--ios-shadow-light);
}
html.zzuli-ios-theme ::selection {
background: rgba(var(--ios-accent-rgb),0.25);
}
/* 顶部导航 */
html.zzuli-ios-theme .navbar.navbar-default {
background: var(--zzuli-card) !important;
backdrop-filter: saturate(180%) blur(18px);
-webkit-backdrop-filter: saturate(180%) blur(18px);
border: none !important;
box-shadow: var(--zzuli-shadow);
margin-bottom: 20px;
border-radius: var(--ios-radius-l);
padding: 4px 20px;
}
html.zzuli-ios-theme .navbar.navbar-default .navbar-brand {
font-weight: 600;
font-size: 18px;
color: var(--zzuli-text) !important;
}
html.zzuli-ios-theme .navbar-nav > li > a {
color: var(--zzuli-text) !important;
border-radius: 20px;
padding: 8px 14px;
margin: 4px 4px;
font-weight: 500;
line-height: 20px;
position: relative;
transition: background-color var(--ios-transition), color var(--ios-transition);
}
html.zzuli-ios-theme .navbar-nav > li.active > a,
html.zzuli-ios-theme .navbar-nav > li > a:hover,
html.zzuli-ios-theme .navbar-nav > li > a:focus {
background: rgba(var(--ios-accent-rgb),0.12) !important;
color: var(--ios-accent) !important;
}
html.zzuli-ios-theme .navbar-toggle {
border: none;
background: rgba(var(--ios-accent-rgb),0.12);
border-radius: 10px;
}
html.zzuli-ios-theme .navbar-toggle .icon-bar {
background: var(--ios-accent);
}
/* 下拉菜单 */
html.zzuli-ios-theme .dropdown-menu {
background: var(--zzuli-card);
backdrop-filter: blur(18px);
border: 1px solid var(--zzuli-separator);
border-radius: var(--ios-radius);
box-shadow: var(--zzuli-shadow);
overflow: hidden;
}
html.zzuli-ios-theme .dropdown-menu > li > a {
padding: 10px 16px;
color: var(--zzuli-text) !important;
transition: background-color var(--ios-transition), color var(--ios-transition);
}
html.zzuli-ios-theme .dropdown-menu > li > a:hover {
background: rgba(var(--ios-accent-rgb),0.15);
color: var(--ios-accent) !important;
}
/* 主体块 / jumbotron => 卡片 */
html.zzuli-ios-theme .jumbotron {
background: var(--zzuli-card) !important;
border-radius: var(--ios-radius-l);
padding: 30px 36px;
box-shadow: var(--zzuli-shadow);
border: 1px solid var(--zzuli-separator);
}
/* 表格 */
html.zzuli-ios-theme table {
width: 100%;
border-collapse: collapse !important;
background: var(--zzuli-card);
border: 1px solid var(--zzuli-separator);
border-radius: var(--ios-radius);
overflow: hidden;
}
html.zzuli-ios-theme table[border],
html.zzuli-ios-theme table td,
html.zzuli-ios-theme table th {
border: none !important;
}
html.zzuli-ios-theme table th {
background: linear-gradient(to bottom, rgba(var(--ios-accent-rgb),0.10), rgba(var(--ios-accent-rgb),0.05));
font-weight: 600;
color: var(--zzuli-text);
padding: 10px 12px;
}
html.zzuli-ios-theme table td {
padding: 8px 12px;
border-top: 1px solid var(--zzuli-separator);
}
html.zzuli-ios-theme table tr:hover td {
background: rgba(var(--ios-accent-rgb),0.07);
}
/* 深色模式表格条纹与背景修复 */
html.zzuli-ios-theme.zzuli-dark .table-striped > tbody > tr:nth-of-type(odd) {
background: rgba(255,255,255,0.05) !important;
}
html.zzuli-ios-theme.zzuli-dark .table-striped > tbody > tr:nth-of-type(even) {
background: transparent !important;
}
html.zzuli-ios-theme.zzuli-dark table tr,
html.zzuli-ios-theme.zzuli-dark table tbody,
html.zzuli-ios-theme.zzuli-dark table thead {
background: transparent !important;
}
/* 分页组件 */
html.zzuli-ios-theme .pagination > li > a,
html.zzuli-ios-theme .pagination > li > span {
background: var(--zzuli-card) !important;
border: 1px solid var(--zzuli-separator) !important;
color: var(--zzuli-text) !important;
transition: background-color var(--ios-transition), color var(--ios-transition), border-color var(--ios-transition);
}
html.zzuli-ios-theme .pagination > li > a:hover,
html.zzuli-ios-theme .pagination > li > span:hover {
background: rgba(var(--ios-accent-rgb),0.18) !important;
color: var(--ios-accent) !important;
}
html.zzuli-ios-theme .pagination > .active > a,
html.zzuli-ios-theme .pagination > .active > span,
html.zzuli-ios-theme .pagination > .active > a:focus,
html.zzuli-ios-theme .pagination > .active > span:focus,
html.zzuli-ios-theme .pagination > .active > a:hover,
html.zzuli-ios-theme .pagination > .active > span:hover {
background: var(--ios-accent) !important;
border-color: var(--ios-accent) !important;
color: #fff !important;
}
html.zzuli-ios-theme.zzuli-dark .pagination > li > a,
html.zzuli-ios-theme.zzuli-dark .pagination > li > span {
background: rgba(60,60,62,0.65) !important;
}
/* Panel / 列表块 修复默认白底 */
html.zzuli-ios-theme .panel,
html.zzuli-ios-theme .panel-default,
html.zzuli-ios-theme .panel-heading,
html.zzuli-ios-theme .panel-body,
html.zzuli-ios-theme .list-group-item {
background: var(--zzuli-card) !important;
border-color: var(--zzuli-separator) !important;
color: var(--zzuli-text) !important;
}
html.zzuli-ios-theme .list-group-item { border: 1px solid var(--zzuli-separator) !important; }
html.zzuli-ios-theme .list-group-item:hover { background: rgba(var(--ios-accent-rgb),0.08) !important; }
/* 按钮 */
html.zzuli-ios-theme .btn,
html.zzuli-ios-theme input[type=button],
html.zzuli-ios-theme input[type=submit],
html.zzuli-ios-theme button {
border-radius: 22px !important;
border: 1px solid rgba(var(--ios-accent-rgb),0.35) !important;
background: linear-gradient(to bottom right, rgba(var(--ios-accent-rgb),0.15), rgba(var(--ios-accent-rgb),0.05)) !important;
color: var(--ios-accent) !important;
font-weight: 500;
padding: 6px 18px;
line-height: 20px;
box-shadow: none !important;
text-shadow: none !important;
transition: background-color var(--ios-transition), border-color var(--ios-transition), color var(--ios-transition);
}
html.zzuli-ios-theme .btn:hover {
background: rgba(var(--ios-accent-rgb),0.25) !important;
}
html.zzuli-ios-theme .btn-primary,
html.zzuli-ios-theme .btn-success {
background: var(--ios-accent) !important;
color: #fff !important;
border: 1px solid var(--ios-accent) !important;
}
html.zzuli-ios-theme .btn-primary:hover {
background: rgba(var(--ios-accent-rgb),0.85) !important;
}
html.zzuli-ios-theme .btn-danger {
background: #FF3B30 !important;
border-color: #FF3B30 !important;
color: #fff !important;
}
/* 链接 */
html.zzuli-ios-theme a {
color: var(--ios-accent);
transition: color var(--ios-transition), background-color var(--ios-transition);
}
html.zzuli-ios-theme a:hover {
color: rgba(var(--ios-accent-rgb),0.8);
text-decoration: none;
}
/* 代码块 */
html.zzuli-ios-theme pre,
html.zzuli-ios-theme code,
html.zzuli-ios-theme pre code {
background: var(--zzuli-code-bg) !important;
border: 1px solid var(--zzuli-separator) !important;
color: var(--zzuli-text) !important;
border-radius: var(--ios-radius);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
padding: 10px 14px;
overflow-x: auto;
}
html.zzuli-ios-theme pre {
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.03);
}
/* 代码编辑器 (提交代码页) 行号栏与暗色优化 */
/* 通用容器与边框 */
html.zzuli-ios-theme .CodeMirror,
html.zzuli-ios-theme .ace_editor {
background: var(--zzuli-code-bg) !important;
border: 1px solid var(--zzuli-separator) !important;
border-radius: var(--ios-radius);
}
/* CodeMirror 行号与 gutter */
html.zzuli-ios-theme .CodeMirror-gutters,
html.zzuli-ios-theme .CodeMirror-gutter { /* 兼容不同版本 */
background: var(--zzuli-card) !important;
border-right: 1px solid var(--zzuli-separator) !important;
}
html.zzuli-ios-theme .CodeMirror-linenumber { color: var(--zzuli-muted) !important; }
html.zzuli-ios-theme .CodeMirror-activeline-background { background: rgba(var(--ios-accent-rgb),0.08) !important; }
html.zzuli-ios-theme.zzuli-dark .CodeMirror-activeline-background { background: rgba(var(--ios-accent-rgb),0.18) !important; }
html.zzuli-ios-theme.zzuli-dark .CodeMirror,
html.zzuli-ios-theme.zzuli-dark .CodeMirror-gutters,
html.zzuli-ios-theme.zzuli-dark .CodeMirror-gutter,
html.zzuli-ios-theme.zzuli-dark .CodeMirror-linenumber { background: var(--zzuli-card) !important; color: var(--zzuli-muted) !important; }
/* Ace Editor Gutter */
html.zzuli-ios-theme .ace_gutter,
html.zzuli-ios-theme .ace_gutter-layer,
html.zzuli-ios-theme .ace_gutter-active-line,
html.zzuli-ios-theme .ace_gutter-cell { background: var(--zzuli-card) !important; color: var(--zzuli-muted) !important; }
html.zzuli-ios-theme .ace_gutter-active-line { background: rgba(var(--ios-accent-rgb),0.10) !important; }
html.zzuli-ios-theme.zzuli-dark .ace_gutter-active-line { background: rgba(var(--ios-accent-rgb),0.22) !important; }
html.zzuli-ios-theme .ace_marker-layer .ace_active-line { background: rgba(var(--ios-accent-rgb),0.08) !important; }
html.zzuli-ios-theme.zzuli-dark .ace_marker-layer .ace_active-line { background: rgba(var(--ios-accent-rgb),0.16) !important; }
html.zzuli-ios-theme.zzuli-dark .ace_gutter,
html.zzuli-ios-theme.zzuli-dark .ace_gutter-layer,
html.zzuli-ios-theme.zzuli-dark .ace_gutter-cell { background: var(--zzuli-card) !important; color: var(--zzuli-muted) !important; }
/* 通用/备用类名(可能的自定义行号容器) */
html.zzuli-ios-theme .line-numbers,
html.zzuli-ios-theme .line-numbers-rows,
html.zzuli-ios-theme .linenums,
html.zzuli-ios-theme .gutter,
html.zzuli-ios-theme.zzuli-dark .line-numbers,
html.zzuli-ios-theme.zzuli-dark .line-numbers-rows,
html.zzuli-ios-theme.zzuli-dark .linenums,
html.zzuli-ios-theme.zzuli-dark .gutter { background: var(--zzuli-card) !important; color: var(--zzuli-muted) !important; }
html.zzuli-ios-theme .line-numbers-rows > span:before { color: var(--zzuli-muted); }
html.zzuli-ios-theme.zzuli-dark .line-numbers-rows > span:before { color: var(--zzuli-muted); }
/* 代码编辑器语法高亮与光标优化(深色模式) */
html.zzuli-ios-theme.zzuli-dark .CodeMirror { color: #e7e9ef !important; }
html.zzuli-ios-theme.zzuli-dark .CodeMirror-cursor { border-left: 2px solid #fff !important; }
html.zzuli-ios-theme.zzuli-dark .CodeMirror-selected { background: rgba(var(--ios-accent-rgb),0.35) !important; }
html.zzuli-ios-theme .CodeMirror-focused .CodeMirror-selected { background: rgba(var(--ios-accent-rgb),0.40) !important; }
/* CodeMirror 5 token colors */
html.zzuli-ios-theme.zzuli-dark .cm-keyword { color: #82AAFF !important; }
html.zzuli-ios-theme.zzuli-dark .cm-operator { color: #89DDFF !important; }
html.zzuli-ios-theme.zzuli-dark .cm-variable { color: #ECEFF4 !important; }
html.zzuli-ios-theme.zzuli-dark .cm-variable-2 { color: #C5E4FF !important; }
html.zzuli-ios-theme.zzuli-dark .cm-variable-3 { color: #F6C177 !important; }
html.zzuli-ios-theme.zzuli-dark .cm-def { color: #B3E5FC !important; }
html.zzuli-ios-theme.zzuli-dark .cm-string { color: #C3E88D !important; }
html.zzuli-ios-theme.zzuli-dark .cm-number { color: #F78C6C !important; }
html.zzuli-ios-theme.zzuli-dark .cm-comment { color: #6b7480 !important; font-style: italic; }
html.zzuli-ios-theme.zzuli-dark .cm-meta { color: #FFCB6B !important; }
html.zzuli-ios-theme.zzuli-dark .cm-builtin { color: #FF9CAC !important; }
html.zzuli-ios-theme.zzuli-dark .cm-tag { color: #7FDBCA !important; }
html.zzuli-ios-theme.zzuli-dark .cm-attribute { color: #F6C177 !important; }
html.zzuli-ios-theme.zzuli-dark .cm-atom { color: #FFCB6B !important; }
html.zzuli-ios-theme.zzuli-dark .cm-error { background: #B00020 !important; color: #fff !important; }
html.zzuli-ios-theme.zzuli-dark .CodeMirror-activeline-background { background: rgba(255,255,255,0.06) !important; }
/* CodeMirror 6 (如果未来升级) */
html.zzuli-ios-theme.zzuli-dark .cm-editor { color: #e7e9ef !important; }
html.zzuli-ios-theme.zzuli-dark .cm-editor .cm-content ::selection { background: rgba(var(--ios-accent-rgb),0.35); }
html.zzuli-ios-theme.zzuli-dark .cm-activeLine { background: rgba(255,255,255,0.06) !important; }
html.zzuli-ios-theme.zzuli-dark .cm-gutters { background: var(--zzuli-card) !important; color: #6b7480 !important; border-right: 1px solid var(--zzuli-separator) !important; }
/* Ace Editor dark overrides */
html.zzuli-ios-theme.zzuli-dark .ace_editor { color: #E7E9EF !important; }
html.zzuli-ios-theme.zzuli-dark .ace_cursor { color: #FFFFFF !important; }
html.zzuli-ios-theme.zzuli-dark .ace_marker-layer .ace_selection { background: rgba(var(--ios-accent-rgb),0.38) !important; }
html.zzuli-ios-theme.zzuli-dark .ace_marker-layer .ace_active-line { background: rgba(255,255,255,0.06) !important; }
html.zzuli-ios-theme.zzuli-dark .ace_gutter { color: #6b7480 !important; }
html.zzuli-ios-theme.zzuli-dark .ace_keyword,
html.zzuli-ios-theme.zzuli-dark .ace_storage { color: #82AAFF !important; }
html.zzuli-ios-theme.zzuli-dark .ace_operator { color: #89DDFF !important; }
html.zzuli-ios-theme.zzuli-dark .ace_identifier,
html.zzuli-ios-theme.zzuli-dark .ace_variable { color: #ECEFF4 !important; }
html.zzuli-ios-theme.zzuli-dark .ace_variable.ace_parameter { color: #C5E4FF !important; }
html.zzuli-ios-theme.zzuli-dark .ace_support.ace_function,
html.zzuli-ios-theme.zzuli-dark .ace_entity.ace_name.ace_function { color: #B3E5FC !important; }
html.zzuli-ios-theme.zzuli-dark .ace_string { color: #C3E88D !important; }
html.zzuli-ios-theme.zzuli-dark .ace_numeric,
html.zzuli-ios-theme.zzuli-dark .ace_constant.ace_numeric { color: #F78C6C !important; }
html.zzuli-ios-theme.zzuli-dark .ace_comment { color: #6b7480 !important; font-style: italic; }
html.zzuli-ios-theme.zzuli-dark .ace_constant.ace_language,
html.zzuli-ios-theme.zzuli-dark .ace_constant.ace_character { color: #FFCB6B !important; }
html.zzuli-ios-theme.zzuli-dark .ace_constant.ace_other { color: #FF9CAC !important; }
html.zzuli-ios-theme.zzuli-dark .ace_invalid { background: #B00020 !important; color: #fff !important; }
html.zzuli-ios-theme.zzuli-dark .ace_markup.ace_heading,
html.zzuli-ios-theme.zzuli-dark .ace_entity.ace_other.ace_attribute-name { color: #F6C177 !important; }
html.zzuli-ios-theme.zzuli-dark .ace_entity.ace_name.ace_tag { color: #7FDBCA !important; }
/* 分割线 */
html.zzuli-ios-theme hr {
border: none;
height: 1px;
background: var(--zzuli-separator);
margin: 24px 0;
}
/* 图片适配圆角 */
html.zzuli-ios-theme img {
border-radius: 8px;
}
/* 表单输入 */
html.zzuli-ios-theme input[type=text],
html.zzuli-ios-theme input[type=password],
html.zzuli-ios-theme select,
html.zzuli-ios-theme textarea {
background: var(--zzuli-card);
border: 1px solid var(--zzuli-separator);
border-radius: 10px;
padding: 6px 10px;
box-shadow: none;
color: var(--zzuli-text);
transition: border-color var(--ios-transition), background-color var(--ios-transition);
}
html.zzuli-ios-theme input:focus,
html.zzuli-ios-theme select:focus,
html.zzuli-ios-theme textarea:focus {
outline: none;
border-color: var(--ios-accent);
}
/* 页脚 / 版权 */
html.zzuli-ios-theme table[width="100%"][border="0"] {
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
/* 适配较窄屏幕 */
@media (max-width: 768px) {
html.zzuli-ios-theme .navbar.navbar-default {
border-radius: 0 0 20px 20px;
margin: 0 0 16px 0;
}
html.zzuli-ios-theme .jumbotron {
padding: 22px 20px;
}
}
/* 控制面板 */
#zzuli-ios-toggle-panel {
position: fixed;
right: 16px;
bottom: 16px;
z-index: 99999;
display: flex;
flex-direction: column;
gap: 8px;
font-family: var(--ios-font-stack);
}
#zzuli-ios-toggle-panel button {
cursor: pointer;
min-width: 140px;
font-size: 13px;
}
#zzuli-ios-toggle-panel .zzuli-chip {
background: var(--zzuli-card);
border: 1px solid var(--zzuli-separator);
border-radius: 14px;
padding: 6px 12px;
font-size: 12px;
color: var(--zzuli-muted);
text-align: center;
backdrop-filter: blur(18px);
}
html:not(.zzuli-ios-theme) #zzuli-ios-toggle-panel .state-indicator {
color: #999;
}
/* 关闭时隐藏效果 */
html:not(.zzuli-ios-theme) .zzuli-ios-wrapped {
all: unset;
}
/* ===== 追加:暗色模式下白块/条纹/底栏修复 ===== */
/* 底部题目页面提交条 panel-footer 白底修复 */
html.zzuli-ios-theme .panel-footer {
background: var(--zzuli-card) !important;
border-top: 1px solid var(--zzuli-separator) !important;
color: var(--zzuli-text) !important;
box-shadow: none !important;
}
html.zzuli-ios-theme.zzuli-dark .panel-footer a { color: var(--ios-accent) !important; }
/* 题目详情页 center 容器可能残留白色 */
html.zzuli-ios-theme.zzuli-dark center,
html.zzuli-ios-theme.zzuli-dark center > div { background: transparent !important; }
/* 自定义 evenrow / oddrow 条纹在暗色模式下统一风格 */
html.zzuli-ios-theme.zzuli-dark tr.evenrow,
html.zzuli-ios-theme.zzuli-dark tr.oddrow { background: transparent !important; }
html.zzuli-ios-theme.zzuli-dark tr.evenrow td { background: rgba(255,255,255,0.04) !important; }
html.zzuli-ios-theme.zzuli-dark tr.oddrow td { background: rgba(255,255,255,0.02) !important; }
html.zzuli-ios-theme.zzuli-dark tr.evenrow:hover td,
html.zzuli-ios-theme.zzuli-dark tr.oddrow:hover td { background: rgba(var(--ios-accent-rgb),0.18) !important; }
/* 兼容可能被写死的 header 背景 */
html.zzuli-ios-theme.zzuli-dark th,
html.zzuli-ios-theme.zzuli-dark thead td { background: rgba(255,255,255,0.05) !important; }
/* select/option 在暗色模式下下拉白底修复 */
html.zzuli-ios-theme.zzuli-dark select,
html.zzuli-ios-theme.zzuli-dark option,
html.zzuli-ios-theme.zzuli-dark select option { background: var(--zzuli-card) !important; color: var(--zzuli-text) !important; }
/* 捕获仍然用内联 #fff/#FFF 的背景块(尽量温和) */
html.zzuli-ios-theme.zzuli-dark [style*="background-color:#FFF"],
html.zzuli-ios-theme.zzuli-dark [style*="background-color:#fff"],
html.zzuli-ios-theme.zzuli-dark [style*="background:#FFF"],
html.zzuli-ios-theme.zzuli-dark [style*="background:#fff"] {
background: var(--zzuli-card) !important;
}
/* 某些内联 style 设为 white 的表格/td 修复 */
html.zzuli-ios-theme.zzuli-dark td[bgcolor="white"],
html.zzuli-ios-theme.zzuli-dark tr[bgcolor="white"],
html.zzuli-ios-theme.zzuli-dark table[bgcolor="white"] { background: transparent !important; }
/* Pie 图所在单元格白底强制透明 */
html.zzuli-ios-theme.zzuli-dark #pie[bgcolor="white"],
html.zzuli-ios-theme.zzuli-dark #pie { background: transparent !important; }
/* 避免分页条在暗色模式出现突兀白底 */
html.zzuli-ios-theme.zzuli-dark .pagination > li > a,
html.zzuli-ios-theme.zzuli-dark .pagination > li > span { background: rgba(60,60,62,0.65) !important; }
/* ===== 追加结束 ===== */
/* ===== 题目详情页专属增强 ===== */
html.zzuli-ios-theme .zzuli-problem-header {
background: var(--zzuli-card);
border: 1px solid var(--zzuli-separator);
box-shadow: var(--zzuli-shadow);
border-radius: 26px;
padding: 28px 32px 22px 32px;
margin-bottom: 26px;
position: relative;
overflow: hidden;
}
html.zzuli-ios-theme .zzuli-problem-header::before {
content: '';
position: absolute; inset:0;
background: radial-gradient(circle at 85% 15%, rgba(var(--ios-accent-rgb),0.18), transparent 55%);
pointer-events:none;
}
html.zzuli-ios-theme.zzuli-dark .zzuli-problem-header::before {
background: radial-gradient(circle at 78% 18%, rgba(var(--ios-accent-rgb),0.28), transparent 60%);
}
html.zzuli-ios-theme .zzuli-problem-title {
font-size: 28px; font-weight: 700; margin:0 0 14px 0; letter-spacing: .5px;
background: linear-gradient(92deg, var(--ios-accent), rgba(var(--ios-accent-rgb),0.55));
-webkit-background-clip: text; color: transparent;
}
html.zzuli-ios-theme .zzuli-problem-meta { display:flex; flex-wrap:wrap; gap:10px 14px; margin-bottom:4px; }
html.zzuli-ios-theme .zzuli-chip-badge { font-size:12px; padding:4px 10px 5px; line-height:1.2; border-radius: 18px; background: rgba(var(--ios-accent-rgb),0.12); color: var(--ios-accent); font-weight:500; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border:1px solid rgba(var(--ios-accent-rgb),0.35); }
html.zzuli-ios-theme.zzuli-dark .zzuli-chip-badge { background: rgba(var(--ios-accent-rgb),0.22); }
html.zzuli-ios-theme .zzuli-chip-badge[data-kind="limit"] { background: rgba(var(--ios-accent-rgb),0.08); }
html.zzuli-ios-theme.zzuli-dark .zzuli-chip-badge[data-kind="limit"] { background: rgba(var(--ios-accent-rgb),0.30); }
html.zzuli-ios-theme .zzuli-problem-actions-bar { display:flex; gap:14px; margin-top:12px; }
html.zzuli-ios-theme .zzuli-problem-actions-bar .zzuli-action-btn { cursor:pointer; border:none; border-radius: 22px; padding:10px 20px; font-size:14px; font-weight:600; background: var(--ios-accent); color:#fff !important; box-shadow: 0 4px 14px -4px rgba(var(--ios-accent-rgb),0.55); transition: transform var(--ios-transition), box-shadow var(--ios-transition), background var(--ios-transition); text-decoration:none !important; display:inline-flex; align-items:center; gap:6px; }
html.zzuli-ios-theme .zzuli-problem-actions-bar .zzuli-action-btn:hover { transform: translateY(-2px); box-shadow:0 6px 18px -6px rgba(var(--ios-accent-rgb),0.65); }
html.zzuli-ios-theme .zzuli-problem-actions-bar .zzuli-action-btn.secondary { background: linear-gradient(to bottom right, rgba(var(--ios-accent-rgb),0.20), rgba(var(--ios-accent-rgb),0.08)); color: var(--ios-accent) !important; box-shadow:none; border:1px solid rgba(var(--ios-accent-rgb),0.35); }
html.zzuli-ios-theme .zzuli-problem-actions-bar .zzuli-action-btn.secondary:hover { background: rgba(var(--ios-accent-rgb),0.30); }
/* 小节 Panel 视觉强化 */
html.zzuli-ios-theme .panel.zzuli-problem-section { position:relative; border-radius:22px; overflow:hidden; }
html.zzuli-ios-theme .panel.zzuli-problem-section::before { content:''; position:absolute; left:0; top:0; bottom:0; width:4px; background: linear-gradient(var(--ios-accent), rgba(var(--ios-accent-rgb),0.4)); }
html.zzuli-ios-theme.zzuli-dark .panel.zzuli-problem-section::before { background: linear-gradient(rgba(var(--ios-accent-rgb),0.85), rgba(var(--ios-accent-rgb),0.35)); }
html.zzuli-ios-theme .panel.zzuli-problem-section .panel-heading { font-weight:600 !important; font-size:16px !important; background: linear-gradient(to right, rgba(var(--ios-accent-rgb),0.16), rgba(var(--ios-accent-rgb),0.06)) !important; border-bottom:1px solid var(--zzuli-separator) !important; }
html.zzuli-ios-theme.zzuli-dark .panel.zzuli-problem-section .panel-heading { background: linear-gradient(to right, rgba(var(--ios-accent-rgb),0.28), rgba(var(--ios-accent-rgb),0.10)) !important; }
/* 样例区代码块微调 */
html.zzuli-ios-theme pre.content { font-size:13.5px; line-height:1.5; }
html.zzuli-ios-theme .sampledata { font-family: ui-monospace, Menlo, Consolas, monospace; }
/* 底部 footer 中的旧方括号隐藏(仅在我们生成新按钮后附加类时生效) */
html.zzuli-ios-theme .panel-footer.zzuli-clean-brackets { font-size:0; }
html.zzuli-ios-theme .panel-footer.zzuli-clean-brackets a { font-size:14px; }
/* 让原 footer 转为透明容器(我们另建按钮栏) */
html.zzuli-ios-theme .panel-footer.zzuli-problem-footer { background:transparent !important; border:none !important; box-shadow:none !important; padding:0 !important; margin-top:12px; }
/* ===== 题目详情页专属增强结束 ===== */
/* ===== 提交代码页增强样式 ===== */
html.zzuli-ios-theme .zzuli-submit-wrapper { background: var(--zzuli-card); border:1px solid var(--zzuli-separator); border-radius:28px; padding:32px 34px 28px; box-shadow: var(--zzuli-shadow); max-width:1200px; margin:0 auto 42px; position:relative; }
html.zzuli-ios-theme .zzuli-submit-wrapper.zzuli-fullscreen { position:fixed; inset:16px; z-index:99990; overflow:auto; backdrop-filter:blur(22px) saturate(180%); -webkit-backdrop-filter:blur(22px) saturate(180%); animation:zzuliFadeIn .18s ease; }
@keyframes zzuliFadeIn { from { opacity:0; transform:scale(.985);} to { opacity:1; transform:scale(1);} }
html.zzuli-ios-theme .zzuli-submit-header { display:flex; flex-wrap:wrap; gap:18px; align-items:center; margin-bottom:18px; }
html.zzuli-ios-theme .zzuli-submit-title { font-size:24px; font-weight:700; background:linear-gradient(92deg, var(--ios-accent), rgba(var(--ios-accent-rgb),0.55)); -webkit-background-clip:text; color:transparent; margin-right:auto; }
html.zzuli-ios-theme .zzuli-lang-segment { display:inline-flex; background:rgba(var(--ios-accent-rgb),0.12); border:1px solid rgba(var(--ios-accent-rgb),0.35); border-radius:20px; padding:4px; gap:4px; }
html.zzuli-ios-theme.zzuli-dark .zzuli-lang-segment { background:rgba(var(--ios-accent-rgb),0.25); }
html.zzuli-ios-theme .zzuli-lang-segment button { border:none; background:transparent; padding:6px 14px; font-size:13px; font-weight:500; border-radius:14px; cursor:pointer; color:var(--ios-accent); transition: background var(--ios-transition), color var(--ios-transition); }
html.zzuli-ios-theme .zzuli-lang-segment button.zzuli-active { background:var(--ios-accent); color:#fff; box-shadow:0 2px 6px -1px rgba(var(--ios-accent-rgb),0.55); }
html.zzuli-ios-theme .zzuli-submit-toolbar { display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin-bottom:10px; }
html.zzuli-ios-theme .zzuli-chip-small { font-size:12px; padding:4px 10px; border-radius:14px; background:rgba(var(--ios-accent-rgb),0.10); color:var(--ios-accent); font-weight:500; border:1px solid rgba(var(--ios-accent-rgb),0.25); }
html.zzuli-ios-theme.zzuli-dark .zzuli-chip-small { background:rgba(var(--ios-accent-rgb),0.22); }
html.zzuli-ios-theme .zzuli-submit-actions { margin-left:auto; display:flex; gap:10px; }
html.zzuli-ios-theme .zzuli-submit-fullscreen-btn { background:linear-gradient(to bottom right, rgba(var(--ios-accent-rgb),0.18), rgba(var(--ios-accent-rgb),0.05)); border:1px solid rgba(var(--ios-accent-rgb),0.4); color:var(--ios-accent); padding:6px 14px; font-size:12px; line-height:1; border-radius:18px; cursor:pointer; }
html.zzuli-ios-theme .zzuli-submit-fullscreen-btn:hover { background:rgba(var(--ios-accent-rgb),0.30); color:#fff; }
html.zzuli-ios-theme .zzuli-editor-container { position:relative; border-radius:18px; overflow:hidden; }
html.zzuli-ios-theme .zzuli-editor-container .CodeMirror,
html.zzuli-ios-theme .zzuli-editor-container .ace_editor,
html.zzuli-ios-theme .zzuli-editor-container textarea { min-height:380px; font-size:13.5px; }
html.zzuli-ios-theme .zzuli-code-overlay { position:absolute; right:10px; bottom:8px; font-size:11px; background:rgba(0,0,0,0.35); color:#fff; padding:4px 10px 5px; border-radius:14px; display:flex; gap:8px; align-items:center; backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); pointer-events:none; }
html.zzuli-ios-theme.zzuli-dark .zzuli-code-overlay { background:rgba(255,255,255,0.15); }
html.zzuli-ios-theme .zzuli-auto-saved { font-size:11px; color:var(--zzuli-muted); margin-left:4px; min-height:14px; }
html.zzuli-ios-theme .zzuli-submit-footer { display:flex; justify-content:space-between; align-items:center; margin-top:18px; gap:12px; flex-wrap:wrap; }
html.zzuli-ios-theme .zzuli-submit-footer .btn-primary { font-weight:600; padding:8px 26px; }
html.zzuli-ios-theme .zzuli-warning-unsaved { color:#d35400; font-size:12px; font-weight:500; }
html.zzuli-ios-theme.zzuli-dark .zzuli-warning-unsaved { color:#ffb347; }
html.zzuli-ios-theme .zzuli-hidden { display:none !important; }
html.zzuli-ios-theme .zzuli-lang-select-original { display:none !important; }
@media (max-width: 768px){
html.zzuli-ios-theme .zzuli-submit-wrapper { padding:24px 20px 26px; border-radius:22px; }
html.zzuli-ios-theme .zzuli-submit-title { font-size:20px; }
html.zzuli-ios-theme .zzuli-submit-fullscreen-btn { padding:6px 12px; }
html.zzuli-ios-theme .zzuli-code-overlay { font-size:10px; right:6px; bottom:6px; }
}
/* ===== 提交代码页增强样式结束 ===== */
/* ===== 代码补全 / 导入提示 iOS 风格 ===== */
/* CodeMirror 5 show-hint */
html.zzuli-ios-theme .CodeMirror-hints { position:absolute; z-index:999999 !important; padding:6px 0; margin:4px 0 0; list-style:none; background:var(--zzuli-card); backdrop-filter:blur(22px) saturate(180%); -webkit-backdrop-filter:blur(22px) saturate(180%); border:1px solid var(--zzuli-separator); border-radius:16px; font-size:13px; box-shadow:0 8px 28px -6px rgba(0,0,0,0.18), 0 2px 6px rgba(0,0,0,0.08); max-height:320px; overflow-y:auto; scrollbar-width:thin; }
html.zzuli-ios-theme.zzuli-dark .CodeMirror-hints { box-shadow:0 8px 28px -8px rgba(0,0,0,0.6), 0 2px 6px rgba(0,0,0,0.5); }
html.zzuli-ios-theme .CodeMirror-hint { padding:6px 14px 6px 14px; cursor:pointer; white-space:nowrap; color:var(--zzuli-text); display:flex; align-items:center; gap:10px; position:relative; font-family:var(--ios-font-stack); }
html.zzuli-ios-theme .CodeMirror-hint:hover { background:rgba(var(--ios-accent-rgb),0.10); }
html.zzuli-ios-theme .CodeMirror-hint-active { background:linear-gradient(92deg, var(--ios-accent), rgba(var(--ios-accent-rgb),0.7)); color:#fff; border-radius:10px; }
html.zzuli-ios-theme .CodeMirror-hint span.zzuli-hint-meta { margin-left:auto; font-size:11px; color:var(--zzuli-muted); font-weight:500; letter-spacing:.5px; }
html.zzuli-ios-theme .CodeMirror-hint-active span.zzuli-hint-meta { color:rgba(255,255,255,0.9); }
/* 右侧导入 / 来源 / 类型提示通用元信息容器(若存在) */
html.zzuli-ios-theme .CodeMirror-hint .zzuli-hint-meta { margin-left:auto; font-size:11px; color:var(--zzuli-muted); font-weight:500; letter-spacing:.5px; }
/* CodeMirror 6 自动补全 */
html.zzuli-ios-theme .cm-tooltip.cm-tooltip-autocomplete { border:1px solid var(--zzuli-separator); background:var(--zzuli-card); backdrop-filter:blur(22px) saturate(180%); -webkit-backdrop-filter:blur(22px) saturate(180%); border-radius:18px; padding:6px 0; box-shadow:0 8px 28px -6px rgba(0,0,0,0.18), 0 2px 6px rgba(0,0,0,0.08); }
html.zzuli-ios-theme.zzuli-dark .cm-tooltip.cm-tooltip-autocomplete { box-shadow:0 8px 28px -8px rgba(0,0,0,0.6), 0 2px 6px rgba(0,0,0,0.5); }
html.zzuli-ios-theme .cm-tooltip-autocomplete ul { padding:0; margin:0; }
html.zzuli-ios-theme .cm-tooltip-autocomplete li { padding:6px 14px; font-size:13px; display:flex; align-items:center; gap:8px; }
html.zzuli-ios-theme .cm-tooltip-autocomplete li[aria-selected="true"] { background:linear-gradient(92deg, var(--ios-accent), rgba(var(--ios-accent-rgb),0.7)); color:#fff; }
html.zzuli-ios-theme .cm-completionDetail { margin-left:auto; font-size:11px; opacity:.75; }
html.zzuli-ios-theme .cm-completionMatchedText { font-weight:600; color:var(--ios-accent); }
html.zzuli-ios-theme.zzuli-dark .cm-tooltip-autocomplete li[aria-selected="true"] .cm-completionMatchedText { color:#fff; text-decoration:underline; }
/* Ace Editor 自动补全 */
html.zzuli-ios-theme .ace_autocomplete { background:var(--zzuli-card) !important; color:var(--zzuli-text) !important; border:1px solid var(--zzuli-separator) !important; border-radius:18px !important; box-shadow:0 8px 26px -6px rgba(0,0,0,0.18),0 2px 6px rgba(0,0,0,0.08) !important; padding:4px 0 6px !important; backdrop-filter:blur(20px) saturate(180%); -webkit-backdrop-filter:blur(20px) saturate(180%); }
html.zzuli-ios-theme.zzuli-dark .ace_autocomplete { box-shadow:0 8px 28px -8px rgba(0,0,0,0.6),0 2px 6px rgba(0,0,0,0.5) !important; }
html.zzuli-ios-theme .ace_autocomplete .ace_completion { padding:4px 14px 4px 14px !important; position:relative; }
html.zzuli-ios-theme .ace_autocomplete .ace_completion:hover { background:rgba(var(--ios-accent-rgb),0.10) !important; }
html.zzuli-ios-theme .ace_autocomplete .ace_selected { background:linear-gradient(92deg, var(--ios-accent), rgba(var(--ios-accent-rgb),0.75)) !important; color:#fff !important; border-radius:10px; }
html.zzuli-ios-theme .ace_autocomplete .ace_completion.ace_selected .ace_rightAlignedText { color:rgba(255,255,255,0.85) !important; }
html.zzuli-ios-theme .ace_autocomplete .ace_rightAlignedText { font-size:11px; color:var(--zzuli-muted) !important; font-weight:500; }
html.zzuli-ios-theme .ace_doc-tooltip { background:var(--zzuli-card) !important; border:1px solid var(--zzuli-separator) !important; color:var(--zzuli-text) !important; border-radius:16px !important; box-shadow:0 10px 30px -8px rgba(0,0,0,0.25),0 2px 6px rgba(0,0,0,0.08) !important; max-width:420px; line-height:1.5; padding:10px 14px !important; font-size:12.5px; }
html.zzuli-ios-theme.zzuli-dark .ace_doc-tooltip { box-shadow:0 10px 34px -10px rgba(0,0,0,0.65),0 2px 6px rgba(0,0,0,0.5) !important; }
html.zzuli-ios-theme .ace_doc-tooltip code, html.zzuli-ios-theme .ace_doc-tooltip pre { background:rgba(var(--ios-accent-rgb),0.10); padding:2px 6px; border-radius:8px; }
/* 滚动条微调 */
html.zzuli-ios-theme .CodeMirror-hints::-webkit-scrollbar, html.zzuli-ios-theme .ace_autocomplete::-webkit-scrollbar { width:8px; }
html.zzuli-ios-theme .CodeMirror-hints::-webkit-scrollbar-track, html.zzuli-ios-theme .ace_autocomplete::-webkit-scrollbar-track { background:transparent; }
html.zzuli-ios-theme .CodeMirror-hints::-webkit-scrollbar-thumb, html.zzuli-ios-theme .ace_autocomplete::-webkit-scrollbar-thumb { background:rgba(var(--ios-accent-rgb),0.35); border-radius:20px; }
html.zzuli-ios-theme.zzuli-dark .CodeMirror-hints::-webkit-scrollbar-thumb, html.zzuli-ios-theme.zzuli-dark .ace_autocomplete::-webkit-scrollbar-thumb { background:rgba(var(--ios-accent-rgb),0.55); }
/* ===== 补全弹窗结束 ===== */
`;
styleTag.textContent = baseCSS;
function applyTheme() {
const html = document.documentElement;
if (STATE.enabled) {
html.classList.add('zzuli-ios-theme');
if (STATE.dark) {
html.classList.add('zzuli-dark');
} else {
html.classList.remove('zzuli-dark');
}
} else {
html.classList.remove('zzuli-ios-theme');
html.classList.remove('zzuli-dark');
}
updatePanelText();
}
// 控制面板
const panel = document.createElement('div');
panel.id = 'zzuli-ios-toggle-panel';
panel.innerHTML = `
<div class="zzuli-chip state-indicator">iOS主题: <span id="zzuli-ios-state"></span></div>
<button id="zzuli-ios-toggle" class="btn btn-sm">切换 iOS 主题</button>
<button id="zzuli-ios-dark" class="btn btn-sm">切换深色模式</button>
<button id="zzuli-ios-auto" class="btn btn-sm">跟随系统: <span id="zzuli-ios-auto-state"></span></button>
<div class="zzuli-chip">快捷键: Ctrl/⌘ + Alt/⌥ + I</div>
`;
document.body.appendChild(panel);
function updatePanelText() {
const state = document.getElementById('zzuli-ios-state');
const autoState = document.getElementById('zzuli-ios-auto-state');
if (state) {
state.textContent = STATE.enabled
? (STATE.dark ? '开启 (深色)' : '开启 (浅色)')
: '关闭';
}
if (autoState) {
autoState.textContent = STATE.auto ? '开' : '关';
}
}
panel.querySelector('#zzuli-ios-toggle').addEventListener('click', toggleEnabled);
panel.querySelector('#zzuli-ios-dark').addEventListener('click', toggleDark);
panel.querySelector('#zzuli-ios-auto').addEventListener('click', toggleAuto);
// 快捷键
window.addEventListener('keydown', (e) => {
const meta = navigator.platform.includes('Mac');
const combo = meta ? e.metaKey && e.altKey : e.ctrlKey && e.altKey;
if (combo && (e.key === 'i' || e.key === 'I')) {
e.preventDefault();
toggleEnabled();
}
});
// 对已有元素做一次必要轻量包装(这里主要依赖纯 CSS 覆盖,不侵入 DOM)
function initialAdjust() {
// 移除可能影响的内联多余背景色 (扩展匹配 #FFF/#fff)
document.querySelectorAll('[style*="background-color:#FFF"],[style*="background-color:#fff"],[style*="background:#FFF"],[style*="background:#fff"]').forEach(el => {
if (!el.dataset._origInlineBg) {
el.dataset._origInlineBg = el.getAttribute('style');
}
try { el.style.backgroundColor = 'transparent'; } catch(e) {}
});
}
/* 题目详情页增强 */
function enhanceProblemPage() {
if (!document.documentElement.classList.contains('zzuli-ios-theme')) return;
// URL 或特征匹配
const isProblem = /problem\.php/i.test(location.href) || document.querySelector('center h3')?.textContent?.match(/^\d+:/);
if (!isProblem) return;
if (document.body.dataset.zzuliProblemEnhanced) return; // 避免重复
document.body.dataset.zzuliProblemEnhanced = '1';
const centerNode = document.querySelector('center h3')?.parentElement?.parentElement; // 依据结构往上两层
if (centerNode) {
// 构建新头部容器
const header = document.createElement('div');
header.className = 'zzuli-problem-header';
// 题目标题
const h3 = centerNode.querySelector('h3');
if (h3) {
const title = document.createElement('div');
title.className = 'zzuli-problem-title';
title.textContent = h3.textContent.trim();
header.appendChild(title);
}
// Meta 信息
const metaWrap = document.createElement('div');
metaWrap.className = 'zzuli-problem-meta';
// 时间限制
const timeSpan = centerNode.querySelector('span[fd="time_limit"]');
if (timeSpan) {
const chip = document.createElement('span');
chip.className = 'zzuli-chip-badge';
chip.dataset.kind = 'limit';
chip.textContent = '时间 ' + timeSpan.textContent.trim() + 's';
metaWrap.appendChild(chip);
}
// 内存限制:直接用全文检索相邻含“内存限制”文本
const memoryNode = Array.from(centerNode.childNodes).find(n => n.textContent && n.textContent.includes('内存限制'));
if (memoryNode) {
const match = memoryNode.textContent.match(/内存限制:\s*([^\s<]+)/);
if (match) {
const chip = document.createElement('span');
chip.className = 'zzuli-chip-badge';
chip.dataset.kind = 'limit';
chip.textContent = '内存 ' + match[1];
metaWrap.appendChild(chip);
}
}
// 提交 / 解决
const statsText = centerNode.textContent;
const submitMatch = statsText.match(/提交:\s*(\d+)/);
const solveMatch = statsText.match(/解决:\s*(\d+)/);
if (submitMatch) {
const chip = document.createElement('span');
chip.className = 'zzuli-chip-badge';
chip.textContent = '提交 ' + submitMatch[1];
metaWrap.appendChild(chip);
}
if (solveMatch) {
const chip = document.createElement('span');
chip.className = 'zzuli-chip-badge';
chip.textContent = '解决 ' + solveMatch[1];
metaWrap.appendChild(chip);
}
// 命题人
const creator = centerNode.querySelector('#creator a');
if (creator) {
const chip = document.createElement('span');
chip.className = 'zzuli-chip-badge';
chip.textContent = '命题人 ' + creator.textContent.trim();
metaWrap.appendChild(chip);
}
header.appendChild(metaWrap);
// 动作按钮栏(提交 / 状态)
const actionsBar = document.createElement('div');
actionsBar.className = 'zzuli-problem-actions-bar';
const submitLink = centerNode.querySelector('a[href*="submitpage.php"]');
const statusLink = centerNode.querySelector('a[href*="problemstatus.php"]');
if (submitLink) {
const a = document.createElement('a');
a.href = submitLink.href;
a.className = 'zzuli-action-btn';
a.textContent = '提交代码';
actionsBar.appendChild(a);
}
if (statusLink) {
const a2 = document.createElement('a');
a2.href = statusLink.href;
a2.className = 'zzuli-action-btn secondary';
a2.textContent = '查看状态';
actionsBar.appendChild(a2);
}
if (actionsBar.children.length) header.appendChild(actionsBar);
// 插入到原 centerNode 前面
centerNode.parentElement.insertBefore(header, centerNode);
}
// 面板强化
document.querySelectorAll('.panel.panel-default').forEach(p => {
p.classList.add('zzuli-problem-section');
});
// 底部 panel-footer 改造(如果存在)
const footer = document.querySelector('center .panel-footer');
if (footer) {
// 移除旧括号并隐藏原文字结构
footer.innerHTML = footer.innerHTML.replace(/[\[\]]/g, '');
footer.classList.add('zzuli-problem-footer');
// 如果我们已经有 header 中的动作按钮,就让 footer 更简洁;否则也复制按钮
if (!document.querySelector('.zzuli-problem-actions-bar')) {
const actionsBar = document.createElement('div');
actionsBar.className = 'zzuli-problem-actions-bar';
footer.querySelectorAll('a').forEach((a, idx) => {
a.classList.add('zzuli-action-btn');
if (idx === 1) a.classList.add('secondary');
a.textContent = a.textContent === '提交' ? '提交代码' : (a.textContent === '状态' ? '查看状态' : a.textContent);
actionsBar.appendChild(a);
});
footer.textContent = '';
footer.appendChild(actionsBar);
} else {
// 已在上方生成;隐藏 footer
footer.style.display = 'none';
}
}
}
// 提交代码页增强
function enhanceSubmitPage(){
if(!document.documentElement.classList.contains('zzuli-ios-theme')) return;
if(!/submitpage\.php/i.test(location.href)) return;
if(document.body.dataset.zzuliSubmitEnhanced) return;
document.body.dataset.zzuliSubmitEnhanced = '1';
const form = document.querySelector('form textarea, form .CodeMirror, form .ace_editor')?.closest('form');
if(!form) return;
const params = new URLSearchParams(location.search);
const pid = params.get('id') || params.get('pid') || params.get('problem_id') || '未知';
const langSelect = form.querySelector('select'); // 保留原生下拉框
const wrapper = document.createElement('div');
wrapper.className = 'zzuli-submit-wrapper';
form.parentElement.insertBefore(wrapper, form);
wrapper.appendChild(form);
const header = document.createElement('div');
header.className = 'zzuli-submit-header';
const title = document.createElement('div');
title.className = 'zzuli-submit-title';
title.textContent = `提交代码 #${pid}`;
header.appendChild(title);
// 不再构建分段语言控件,保留原 select
wrapper.insertBefore(header, form);
const toolbar = document.createElement('div');
toolbar.className = 'zzuli-submit-toolbar';
const statsChip = document.createElement('div'); statsChip.className='zzuli-chip-small'; statsChip.textContent='长度: 0'; toolbar.appendChild(statsChip);
const autoSaved = document.createElement('div'); autoSaved.className='zzuli-auto-saved'; toolbar.appendChild(autoSaved);
wrapper.insertBefore(toolbar, form);
const submitBtn = form.querySelector('input[type=submit],button[type=submit],input[name=submit]');
const footer = document.createElement('div'); footer.className='zzuli-submit-footer';
const leftInfo = document.createElement('div'); leftInfo.className='zzuli-warning-unsaved zzuli-hidden'; leftInfo.textContent='有未保存的更改';
const rightActions = document.createElement('div'); rightActions.style.display='flex'; rightActions.style.gap='10px';
if(submitBtn){ submitBtn.classList.add('btn','btn-primary'); rightActions.appendChild(submitBtn); }
footer.appendChild(leftInfo); footer.appendChild(rightActions); form.appendChild(footer);
const codeNode = form.querySelector('.CodeMirror, .ace_editor, textarea');
if(codeNode){
const container = document.createElement('div'); container.className='zzuli-editor-container';
codeNode.parentElement.insertBefore(container, codeNode); container.appendChild(codeNode);
const overlay = document.createElement('div'); overlay.className='zzuli-code-overlay'; overlay.textContent=''; container.appendChild(overlay);
overlay.dataset.role='overlayStats';
}
const textarea = form.querySelector('textarea');
function getCode(){
const cmEl = form.querySelector('.CodeMirror'); if(cmEl && cmEl.CodeMirror) return cmEl.CodeMirror.getValue();
const aceEl = form.querySelector('.ace_editor'); if(window.ace && aceEl && aceEl.env && aceEl.env.editor) return aceEl.env.editor.getValue();
return textarea ? textarea.value : '';
}
function setCode(v){
const cmEl = form.querySelector('.CodeMirror'); if(cmEl && cmEl.CodeMirror){ cmEl.CodeMirror.setValue(v); return; }
const aceEl = form.querySelector('.ace_editor'); if(window.ace && aceEl && aceEl.env && aceEl.env.editor){ aceEl.env.editor.setValue(v,-1); return; }
if(textarea) textarea.value = v;
}
function draftKey(){ const langIdx = langSelect ? langSelect.selectedIndex : 0; return `zzuli_draft_${pid}_lang${langIdx}`; }
function loadDraftForLang(){ const saved = localStorage.getItem(draftKey()); if(saved!=null){ setCode(saved); lastSavedValue=saved; updateStats(); autoSaved.textContent='已载入草稿'; setTimeout(()=>{ if(autoSaved.textContent==='已载入草稿') autoSaved.textContent='';},1500);} else { updateStats(); } }
let lastSavedValue = getCode();
let debounceTimer = null;
function scheduleAutosave(){
if(debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(()=>{ if(getCode()!==lastSavedValue) performAutosave(); },1500);
}
function performAutosave(){
const code = getCode(); localStorage.setItem(draftKey(), code); lastSavedValue=code; autoSaved.textContent='已自动保存';
setTimeout(()=>{ if(autoSaved.textContent==='已自动保存') autoSaved.textContent=''; },1800); updateStats();
}
function updateStats(){
const code = getCode(); const lines = code.length? code.split(/\n/).length : 0;
statsChip.textContent = `长度: ${code.length} | 行: ${lines}`;
leftInfo.classList.toggle('zzuli-hidden', code===lastSavedValue);
const overlay = form.querySelector('.zzuli-code-overlay'); if(overlay){
let lineInfo='';
const cmEl = form.querySelector('.CodeMirror');
if(cmEl && cmEl.CodeMirror){ const pos = cmEl.CodeMirror.getCursor(); lineInfo = `Ln ${pos.line+1}, Col ${pos.ch+1}`; }
else { const aceEl = form.querySelector('.ace_editor'); if(window.ace && aceEl && aceEl.env && aceEl.env.editor){ const pos = aceEl.env.editor.getCursorPosition(); lineInfo = `Ln ${pos.row+1}, Col ${pos.column+1}`; } }
overlay.textContent = `${lineInfo}${lineInfo? ' | ' : ''}Len ${code.length} Lns ${lines}`;
}
}
if(textarea) textarea.addEventListener('input', ()=>{ updateStats(); scheduleAutosave(); });
const cmEl = form.querySelector('.CodeMirror'); if(cmEl && cmEl.CodeMirror){ cmEl.CodeMirror.on('change', ()=>{ updateStats(); scheduleAutosave(); }); cmEl.CodeMirror.on('cursorActivity', updateStats); }
const aceEl = form.querySelector('.ace_editor'); if(window.ace && aceEl && aceEl.env && aceEl.env.editor){ aceEl.env.editor.on('change', ()=>{ updateStats(); scheduleAutosave(); }); aceEl.env.editor.selection.on('changeCursor', updateStats); }
if(langSelect){ langSelect.addEventListener('change', ()=>{ loadDraftForLang(); }); }
if(submitBtn){ submitBtn.addEventListener('click', ()=>{ const k=draftKey(); setTimeout(()=>localStorage.removeItem(k),200); lastSavedValue=getCode(); updateStats(); }); }
window.addEventListener('beforeunload', (e)=>{ if(getCode()!==lastSavedValue){ e.preventDefault(); e.returnValue=''; }});
loadDraftForLang();
updateStats();
}
// 监听后续变化(若有异步加载)
const observer = new MutationObserver(() => {
if (!STATE.enabled) return;
// 保留位置,后续可添加对动态节点的增强
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
// 初始化
initialAdjust();
applyTheme();
updatePanelText();
// 延迟执行题目增强与提交页增强
setTimeout(()=>{ enhanceProblemPage(); enhanceSubmitPage(); },0);
// ===== 补全提示后处理(拆分导入 / 来源 meta) =====
const hintObserver = new MutationObserver((muts)=>{
muts.forEach(m=>{
if(!(m.addedNodes||m.removedNodes)) return;
document.querySelectorAll('.CodeMirror-hints').forEach(ul=>{
ul.querySelectorAll('li.CodeMirror-hint').forEach(li=>{
if(li.dataset.zzuliHintProcessed) return;
const txt = li.textContent || '';
// 匹配常见格式:identifier — source / identifier - source / identifier · source / identifier (source)
let match, namePart, metaPart;
if((match = txt.match(/^(.+?)\s+[—\-·]\s+(.+?)$/))){
namePart = match[1]; metaPart = match[2];
} else if((match = txt.match(/^(.+?)\s+\(([^()]+)\)$/))){
namePart = match[1]; metaPart = match[2];
}
if(namePart && metaPart){
li.textContent = '';
const spanLeft = document.createElement('span'); spanLeft.textContent = namePart.trim();
const spanMeta = document.createElement('span'); spanMeta.textContent = metaPart.trim(); spanMeta.className='zzuli-hint-meta';
li.appendChild(spanLeft); li.appendChild(spanMeta);
li.dataset.zzuliHintProcessed = '1';
} else {
li.dataset.zzuliHintProcessed = '1';
}
});
});
});
});
hintObserver.observe(document.body,{ childList:true, subtree:true });
})();