Extract duplicated diff rendering logic into shared/diff-page.js Implement theme switcher component across all templates
414 lines
14 KiB
HTML
414 lines
14 KiB
HTML
<!doctype html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>CYBER_DIFF // 2077</title>
|
|
|
|
<!-- Highlight.js 主题 (Atom One Dark - 适合暗黑模式) -->
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css"
|
|
/>
|
|
<!-- Diff2Html 核心样式 -->
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/css/diff2html.min.css"
|
|
/>
|
|
|
|
<!-- 引入赛博朋克风格字体 -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&family=Orbitron:wght@400;700;900&display=swap"
|
|
rel="stylesheet"
|
|
/>
|
|
|
|
<!-- 引入 Tailwind CSS -->
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
<!-- 配置 Tailwind -->
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
cyber: {
|
|
bg: "#050505",
|
|
panel: "#0c0c0c",
|
|
cyan: "#00f0ff",
|
|
pink: "#ff003c",
|
|
yellow: "#fcee0a",
|
|
dark: "#111",
|
|
border: "#333",
|
|
},
|
|
},
|
|
fontFamily: {
|
|
cyber: ['"Orbitron"', "sans-serif"],
|
|
mono: ['"Fira Code"', "monospace"],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style type="text/tailwindcss">
|
|
@layer components {
|
|
/* 赛博朋克按钮 */
|
|
.cyber-btn {
|
|
@apply relative px-6 py-2 font-cyber font-bold uppercase tracking-widest text-sm transition-all duration-200 border-2 bg-transparent;
|
|
clip-path: polygon(
|
|
10px 0,
|
|
100% 0,
|
|
100% calc(100% - 10px),
|
|
calc(100% - 10px) 100%,
|
|
0 100%,
|
|
0 10px
|
|
);
|
|
}
|
|
.cyber-btn-cyan {
|
|
@apply border-cyber-cyan text-cyber-cyan hover:bg-cyber-cyan hover:text-black hover:shadow-[0_0_15px_#00f0ff];
|
|
}
|
|
.cyber-btn-pink {
|
|
@apply border-cyber-pink text-cyber-pink hover:bg-cyber-pink hover:text-black hover:shadow-[0_0_15px_#ff003c];
|
|
}
|
|
.cyber-btn-yellow {
|
|
@apply border-cyber-yellow bg-cyber-yellow text-black hover:bg-transparent hover:text-cyber-yellow hover:shadow-[0_0_15px_#fcee0a];
|
|
}
|
|
|
|
/* 视图切换按钮 */
|
|
.view-option {
|
|
@apply px-4 py-1 font-cyber text-xs tracking-wider text-gray-500 border border-transparent hover:text-cyber-cyan transition-colors;
|
|
}
|
|
.view-option.active {
|
|
@apply text-cyber-cyan border-cyber-cyan bg-cyber-cyan/10 shadow-[inset_0_0_10px_rgba(0,240,255,0.2)];
|
|
}
|
|
.style-switcher {
|
|
@apply flex items-center gap-3;
|
|
}
|
|
.style-switcher-label {
|
|
@apply text-xs text-gray-500 tracking-widest;
|
|
}
|
|
.style-switcher-select {
|
|
background: #050505;
|
|
border: 1px solid #333;
|
|
color: #fcee0a;
|
|
padding: 0.25rem 0.75rem;
|
|
font-family: "Fira Code", monospace;
|
|
font-size: 0.75rem;
|
|
letter-spacing: 0.12em;
|
|
outline: none;
|
|
}
|
|
.style-switcher-select:hover,
|
|
.style-switcher-select:focus {
|
|
border-color: #00f0ff;
|
|
box-shadow: 0 0 12px rgba(0, 240, 255, 0.15);
|
|
}
|
|
|
|
/* 面板边框发光效果 */
|
|
.cyber-panel {
|
|
@apply bg-cyber-panel border border-cyber-border relative;
|
|
}
|
|
.cyber-panel::before {
|
|
content: "";
|
|
@apply absolute top-0 left-0 w-2 h-2 border-t-2 border-l-2 border-cyber-cyan pointer-events-none;
|
|
}
|
|
.cyber-panel::after {
|
|
content: "";
|
|
@apply absolute bottom-0 right-0 w-2 h-2 border-b-2 border-r-2 border-cyber-pink pointer-events-none;
|
|
}
|
|
}
|
|
|
|
/* ==========================================
|
|
强制覆盖 Diff2Html 为暗黑模式 (核心)
|
|
========================================== */
|
|
.d2h-wrapper {
|
|
font-family: "Fira Code", monospace !important;
|
|
color: #e2e8f0 !important;
|
|
}
|
|
.d2h-file-header {
|
|
display: none !important;
|
|
}
|
|
.d2h-file-wrapper {
|
|
border: none !important;
|
|
background: #0c0c0c !important;
|
|
margin-bottom: 0 !important;
|
|
}
|
|
.d2h-code-line-ctn {
|
|
@apply font-mono text-[14px] !important;
|
|
color: #e2e8f0 !important;
|
|
}
|
|
|
|
/* 差异行背景色 (暗黑适配) */
|
|
.d2h-ins {
|
|
background-color: rgba(0, 240, 255, 0.15) !important;
|
|
border-color: #00f0ff !important;
|
|
}
|
|
.d2h-del {
|
|
background-color: rgba(255, 0, 60, 0.15) !important;
|
|
border-color: #ff003c !important;
|
|
}
|
|
|
|
/* 行号区域暗黑适配 */
|
|
.d2h-code-linenumber {
|
|
background-color: #111 !important;
|
|
border-color: #333 !important;
|
|
color: #666 !important;
|
|
}
|
|
.d2h-info {
|
|
background-color: #1a1a1a !important;
|
|
color: #888 !important;
|
|
border-color: #333 !important;
|
|
}
|
|
.d2h-emptyplaceholder {
|
|
background-color: #0c0c0c !important;
|
|
border-color: #333 !important;
|
|
}
|
|
tbody {
|
|
border-color: #333 !important;
|
|
}
|
|
td {
|
|
border-color: #333 !important;
|
|
}
|
|
|
|
/* 滚动条赛博化 */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: #050505;
|
|
border-left: 1px solid #1f2937;
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
background: #333;
|
|
border-radius: 0;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #00f0ff;
|
|
}
|
|
|
|
/* 文本框选中颜色 */
|
|
::selection {
|
|
background: #ff003c;
|
|
color: #fff;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body
|
|
class="bg-cyber-bg font-cyber p-4 md:p-8 min-h-screen flex flex-col items-center text-gray-300"
|
|
>
|
|
<!-- 顶部装饰线 -->
|
|
<div
|
|
class="fixed top-0 left-0 w-full h-1 bg-gradient-to-r from-cyber-cyan via-cyber-pink to-cyber-yellow z-50"
|
|
></div>
|
|
|
|
<div class="max-w-[1600px] w-full flex flex-col gap-6">
|
|
<!-- 标题区 -->
|
|
<header
|
|
class="flex items-end justify-between border-b border-cyber-border pb-4 relative"
|
|
>
|
|
<div>
|
|
<h1
|
|
class="font-black text-3xl md:text-4xl text-white tracking-widest flex items-center gap-3 drop-shadow-[0_0_10px_rgba(255,255,255,0.3)]"
|
|
>
|
|
CYBER<span class="text-cyber-pink">_</span>DIFF
|
|
</h1>
|
|
<p class="text-cyber-cyan text-xs tracking-[0.3em] mt-2 opacity-80">
|
|
NEURAL_NETWORK_CODE_COMPARISON_SYS_V2.0
|
|
</p>
|
|
</div>
|
|
<div class="hidden md:block text-right">
|
|
<div class="text-cyber-yellow text-xs tracking-widest animate-pulse">
|
|
SYS.STATUS // ONLINE
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- 双栏输入区 -->
|
|
<div class="flex flex-col lg:flex-row gap-6">
|
|
<!-- 左侧面板 -->
|
|
<div class="flex-1 flex flex-col cyber-panel">
|
|
<div
|
|
class="flex items-center justify-between px-4 py-2 bg-cyber-dark border-b border-cyber-border"
|
|
>
|
|
<label
|
|
class="text-cyber-cyan text-sm tracking-widest flex items-center gap-2"
|
|
>
|
|
<span
|
|
class="w-2 h-2 bg-cyber-cyan rounded-full animate-pulse"
|
|
></span>
|
|
INPUT_A // ORIGINAL
|
|
</label>
|
|
<button
|
|
id="clearLeftBtn"
|
|
class="text-xs text-gray-500 hover:text-cyber-pink transition-colors tracking-widest"
|
|
>
|
|
[ CLEAR ]
|
|
</button>
|
|
</div>
|
|
<textarea
|
|
id="leftTextarea"
|
|
class="w-full flex-1 min-h-[260px] h-[300px] p-4 font-mono text-[14px] leading-relaxed bg-transparent border-none outline-none resize-y text-gray-300 placeholder:text-gray-700 focus:shadow-[inset_0_0_20px_rgba(0,240,255,0.05)] transition-shadow"
|
|
spellcheck="false"
|
|
>
|
|
function initCyberware() {
|
|
console.log("Booting optics...");
|
|
return "Ready";
|
|
}</textarea
|
|
>
|
|
</div>
|
|
|
|
<!-- 右侧面板 -->
|
|
<div class="flex-1 flex flex-col cyber-panel">
|
|
<div
|
|
class="flex items-center justify-between px-4 py-2 bg-cyber-dark border-b border-cyber-border"
|
|
>
|
|
<label
|
|
class="text-cyber-pink text-sm tracking-widest flex items-center gap-2"
|
|
>
|
|
<span
|
|
class="w-2 h-2 bg-cyber-pink rounded-full animate-pulse"
|
|
></span>
|
|
INPUT_B // MODIFIED
|
|
</label>
|
|
<button
|
|
id="clearRightBtn"
|
|
class="text-xs text-gray-500 hover:text-cyber-cyan transition-colors tracking-widest"
|
|
>
|
|
[ CLEAR ]
|
|
</button>
|
|
</div>
|
|
<textarea
|
|
id="rightTextarea"
|
|
class="w-full flex-1 min-h-[260px] h-[300px] p-4 font-mono text-[14px] leading-relaxed bg-transparent border-none outline-none resize-y text-gray-300 placeholder:text-gray-700 focus:shadow-[inset_0_0_20px_rgba(255,0,60,0.05)] transition-shadow"
|
|
spellcheck="false"
|
|
>
|
|
function initCyberware() {
|
|
console.log("Booting Kiroshi optics v3.0...");
|
|
connectToNetwork();
|
|
return "Online";
|
|
}</textarea
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 工具栏 -->
|
|
<div
|
|
class="flex flex-wrap items-center justify-between gap-4 bg-cyber-dark p-4 border border-cyber-border relative"
|
|
>
|
|
<!-- 装饰角 -->
|
|
<div class="absolute top-0 left-0 w-1 h-1 bg-cyber-yellow"></div>
|
|
<div class="absolute bottom-0 right-0 w-1 h-1 bg-cyber-yellow"></div>
|
|
|
|
<div class="flex flex-wrap items-center gap-6">
|
|
<!-- 语言选择 -->
|
|
<div class="flex items-center gap-3">
|
|
<label class="text-xs text-gray-500 tracking-widest">LANG_</label>
|
|
<select
|
|
id="languageSelect"
|
|
class="bg-cyber-bg border border-cyber-border text-cyber-cyan px-3 py-1 text-sm font-mono outline-none cursor-pointer hover:border-cyber-cyan transition-colors focus:border-cyber-cyan appearance-none"
|
|
>
|
|
<option value="javascript" selected>JS</option>
|
|
<option value="typescript">TS</option>
|
|
<option value="html">HTML</option>
|
|
<option value="css">CSS</option>
|
|
<option value="json">JSON</option>
|
|
<option value="python">PY</option>
|
|
<option value="java">JAVA</option>
|
|
<option value="cpp">CPP</option>
|
|
<option value="plaintext">TXT</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- 视图切换 -->
|
|
<div
|
|
class="flex items-center gap-1 bg-cyber-bg border border-cyber-border p-1"
|
|
id="viewToggle"
|
|
>
|
|
<button class="view-option active" data-view="side-by-side">
|
|
SPLIT
|
|
</button>
|
|
<button class="view-option" data-view="line-by-line">
|
|
UNIFIED
|
|
</button>
|
|
</div>
|
|
|
|
<div data-style-switcher></div>
|
|
</div>
|
|
|
|
<!-- 操作按钮 -->
|
|
<div class="flex gap-4 flex-wrap">
|
|
<button id="swapBtn" class="cyber-btn cyber-btn-cyan">SWAP</button>
|
|
<button id="exampleBtn" class="cyber-btn cyber-btn-pink">DEMO</button>
|
|
<button id="compareBtn" class="cyber-btn cyber-btn-yellow">
|
|
RUN_DIFF
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 差异结果区域 -->
|
|
<div class="cyber-panel flex flex-col">
|
|
<div
|
|
class="px-4 py-3 bg-cyber-dark border-b border-cyber-border flex items-center justify-between"
|
|
>
|
|
<span class="text-white tracking-widest text-sm"
|
|
>OUTPUT // DIFF_RESULT</span
|
|
>
|
|
<span id="diffStats" class="text-xs font-mono tracking-wider"></span>
|
|
</div>
|
|
<div
|
|
id="diff-output"
|
|
class="overflow-x-auto bg-cyber-panel min-h-[150px]"
|
|
>
|
|
<div class="p-10 text-center font-mono text-gray-600 tracking-widest">
|
|
AWAITING EXECUTION...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 依赖库 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/diff@5.1.0/dist/diff.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
|
|
|
<script src="../shared/diff-page.js"></script>
|
|
<script>
|
|
window.initDiffPage({
|
|
themeId: "cyberpunk",
|
|
currentThemePath: "cyberpunk/index.html",
|
|
switcherLabel: "SKIN_",
|
|
switcherAriaLabel: "Switch cyberpunk skin",
|
|
fileLabels: {
|
|
left: "ORIGINAL",
|
|
right: "MODIFIED",
|
|
},
|
|
example: {
|
|
left: `function initCyberware() {\n console.log("Booting optics...");\n return "Ready";\n}`,
|
|
right: `function initCyberware() {\n console.log("Booting Kiroshi optics v3.0...");\n connectToNetwork();\n return "Online";\n}`,
|
|
language: "javascript",
|
|
},
|
|
messages: {
|
|
generateError: (error) =>
|
|
`<div class="p-10 text-center font-mono text-cyber-pink tracking-widest">SYS_ERR: ${error.message}</div>`,
|
|
renderError: () =>
|
|
'<div class="p-10 text-center font-mono text-cyber-pink tracking-widest">RENDER_FAILED</div>',
|
|
blankResult:
|
|
'<div class="p-10 text-center font-mono text-gray-500 tracking-widest">DATA_MATCH // NO_DIFF_FOUND</div>',
|
|
blankStats: "<span class='text-gray-500'>SYNCED</span>",
|
|
generateErrorStats: "ERR",
|
|
renderErrorStats: "ERR",
|
|
},
|
|
formatStats: ({ added, deleted, identical }) => {
|
|
if (identical) {
|
|
return "<span class='text-gray-500'>SYNCED // NO_CHANGES</span>";
|
|
}
|
|
return `<span class='text-cyber-cyan'>+${added} INS</span> <span class='text-gray-600 mx-2'>|</span> <span class='text-cyber-pink'>-${deleted} DEL</span>`;
|
|
},
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|