Extract duplicated diff rendering logic into shared/diff-page.js Implement theme switcher component across all templates
483 lines
16 KiB
HTML
483 lines
16 KiB
HTML
<!doctype html>
|
||
<html lang="zh">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width, initial-scale=1.0, maximum-scale=1.5, user-scalable=yes"
|
||
/>
|
||
<title>V A P O R _ D I F F</title>
|
||
|
||
<!-- Highlight.js 主题 (Shades of Purple - 完美契合蒸汽波) -->
|
||
<link
|
||
rel="stylesheet"
|
||
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/shades-of-purple.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=Righteous&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap"
|
||
rel="stylesheet"
|
||
/>
|
||
|
||
<!-- 引入 Tailwind CSS -->
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
|
||
<!-- 配置 Tailwind -->
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
vapor: {
|
||
bg: "#120458" /* 深邃夜空蓝 */,
|
||
dark: "#2a0a4a" /* 暗紫色面板底色 */,
|
||
pink: "#ff71ce" /* 迈阿密粉 */,
|
||
cyan: "#01cdfe" /* 霓虹青 */,
|
||
purple: "#b967ff" /* 亮紫色 */,
|
||
yellow: "#fffb96" /* 落日黄 */,
|
||
panel: "rgba(42, 10, 74, 0.75)" /* 半透明面板 */,
|
||
},
|
||
},
|
||
fontFamily: {
|
||
vapor: ['"Righteous"', "cursive"],
|
||
mono: ['"Space Mono"', "monospace"],
|
||
},
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style type="text/tailwindcss">
|
||
@layer components {
|
||
/* 蒸汽波半透明发光面板 */
|
||
.vapor-panel {
|
||
@apply bg-vapor-panel backdrop-blur-md border-2 border-vapor-purple shadow-[0_0_20px_rgba(185,103,255,0.3)] relative overflow-hidden;
|
||
}
|
||
|
||
/* 蒸汽波输入框 */
|
||
.vapor-input {
|
||
@apply bg-transparent text-vapor-cyan border-none outline-none resize-y placeholder:text-vapor-cyan/40 focus:shadow-[inset_0_0_30px_rgba(1,205,254,0.15)] transition-all;
|
||
text-shadow: 0 0 4px rgba(1, 205, 254, 0.4);
|
||
}
|
||
|
||
/* 蒸汽波霓虹按钮 */
|
||
.vapor-btn {
|
||
@apply bg-transparent border-2 px-5 py-2 font-vapor tracking-widest uppercase transition-all duration-300 cursor-pointer;
|
||
}
|
||
.vapor-btn-cyan {
|
||
@apply border-vapor-cyan text-vapor-cyan hover:bg-vapor-cyan hover:text-vapor-bg shadow-[0_0_10px_rgba(1,205,254,0.5)] hover:shadow-[0_0_25px_rgba(1,205,254,0.8)];
|
||
}
|
||
.vapor-btn-pink {
|
||
@apply border-vapor-pink text-vapor-pink hover:bg-vapor-pink hover:text-vapor-bg shadow-[0_0_10px_rgba(255,113,206,0.5)] hover:shadow-[0_0_25px_rgba(255,113,206,0.8)];
|
||
}
|
||
|
||
/* 视图切换按钮 */
|
||
.view-option {
|
||
@apply px-3 py-1 font-vapor text-sm tracking-wider text-vapor-purple hover:text-vapor-pink transition-colors;
|
||
}
|
||
.view-option.active {
|
||
@apply text-vapor-bg bg-vapor-purple shadow-[0_0_15px_#b967ff];
|
||
}
|
||
.style-switcher {
|
||
@apply flex items-center gap-3;
|
||
}
|
||
.style-switcher-label {
|
||
@apply font-vapor text-sm tracking-widest text-vapor-yellow drop-shadow-[0_0_5px_#fffb96];
|
||
}
|
||
.style-switcher-select {
|
||
background: #120458;
|
||
border: 2px solid #b967ff;
|
||
color: #01cdfe;
|
||
padding: 0.375rem 0.75rem;
|
||
font-family: "Righteous", cursive;
|
||
font-size: 0.875rem;
|
||
outline: none;
|
||
}
|
||
.style-switcher-select:hover,
|
||
.style-switcher-select:focus {
|
||
border-color: #01cdfe;
|
||
box-shadow: 0 0 10px #01cdfe;
|
||
}
|
||
}
|
||
|
||
/* ==========================================
|
||
纯 CSS 绘制:80年代落日与 3D 透视网格
|
||
========================================== */
|
||
body {
|
||
background: linear-gradient(
|
||
to bottom,
|
||
#0f023b 0%,
|
||
#2a0a4a 60%,
|
||
#ff71ce 100%
|
||
);
|
||
background-attachment: fixed;
|
||
}
|
||
|
||
/* 霓虹落日 */
|
||
.vapor-sun {
|
||
position: fixed;
|
||
top: 15%;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 400px;
|
||
height: 400px;
|
||
background: linear-gradient(to bottom, #fffb96 0%, #ff71ce 100%);
|
||
border-radius: 50%;
|
||
z-index: -2;
|
||
box-shadow:
|
||
0 0 80px #ff71ce,
|
||
0 0 150px #fffb96;
|
||
/* 百叶窗切割效果 */
|
||
-webkit-mask-image: repeating-linear-gradient(
|
||
to bottom,
|
||
black 0%,
|
||
black 6%,
|
||
transparent 6%,
|
||
transparent 8%
|
||
);
|
||
mask-image: repeating-linear-gradient(
|
||
to bottom,
|
||
black 0%,
|
||
black 6%,
|
||
transparent 6%,
|
||
transparent 8%
|
||
);
|
||
}
|
||
|
||
/* 3D 赛博网格地板 */
|
||
.vapor-grid {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: -50%;
|
||
width: 200%;
|
||
height: 40vh;
|
||
background-image:
|
||
linear-gradient(
|
||
to right,
|
||
rgba(1, 205, 254, 0.6) 2px,
|
||
transparent 2px
|
||
),
|
||
linear-gradient(to top, rgba(1, 205, 254, 0.6) 2px, transparent 2px);
|
||
background-size: 50px 50px;
|
||
transform: perspective(600px) rotateX(70deg);
|
||
z-index: -1;
|
||
animation: gridMove 3s linear infinite;
|
||
/* 让网格向远处渐隐 */
|
||
-webkit-mask-image: linear-gradient(
|
||
to bottom,
|
||
transparent 0%,
|
||
black 100%
|
||
);
|
||
}
|
||
@keyframes gridMove {
|
||
0% {
|
||
background-position: 0 0;
|
||
}
|
||
100% {
|
||
background-position: 0 50px;
|
||
}
|
||
}
|
||
|
||
/* ==========================================
|
||
覆盖 Diff2Html 样式 (蒸汽波化)
|
||
========================================== */
|
||
.d2h-wrapper {
|
||
font-family: "Space Mono", monospace !important;
|
||
color: #fff !important;
|
||
}
|
||
.d2h-file-header {
|
||
display: none !important;
|
||
}
|
||
.d2h-file-wrapper {
|
||
border: none !important;
|
||
background: transparent !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
.d2h-code-line-ctn {
|
||
@apply font-mono text-[14px] !important;
|
||
color: #e2e8f0 !important;
|
||
}
|
||
|
||
/* 差异行背景色 (青色代表新增,粉色代表删除) */
|
||
.d2h-ins {
|
||
background-color: rgba(1, 205, 254, 0.2) !important;
|
||
border-color: #01cdfe !important;
|
||
color: #01cdfe !important;
|
||
text-shadow: 0 0 5px rgba(1, 205, 254, 0.5);
|
||
}
|
||
.d2h-del {
|
||
background-color: rgba(255, 113, 206, 0.2) !important;
|
||
border-color: #ff71ce !important;
|
||
color: #ff71ce !important;
|
||
text-shadow: 0 0 5px rgba(255, 113, 206, 0.5);
|
||
}
|
||
|
||
/* 行号区域 */
|
||
.d2h-code-linenumber {
|
||
background-color: rgba(18, 4, 88, 0.8) !important;
|
||
border-color: #b967ff !important;
|
||
color: #b967ff !important;
|
||
}
|
||
.d2h-info {
|
||
background-color: rgba(42, 10, 74, 0.9) !important;
|
||
color: #fffb96 !important;
|
||
border-color: #b967ff !important;
|
||
}
|
||
.d2h-emptyplaceholder {
|
||
background-color: rgba(18, 4, 88, 0.5) !important;
|
||
border-color: #b967ff !important;
|
||
}
|
||
tbody {
|
||
border-color: #b967ff !important;
|
||
}
|
||
td {
|
||
border-color: #b967ff !important;
|
||
}
|
||
|
||
/* 滚动条霓虹化 */
|
||
::-webkit-scrollbar {
|
||
width: 10px;
|
||
height: 10px;
|
||
}
|
||
::-webkit-scrollbar-track {
|
||
background: #120458;
|
||
border-left: 1px solid #b967ff;
|
||
}
|
||
::-webkit-scrollbar-thumb {
|
||
background: #ff71ce;
|
||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||
}
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: #01cdfe;
|
||
}
|
||
|
||
/* 选中文本颜色 */
|
||
::selection {
|
||
background: #ff71ce;
|
||
color: #fff;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body
|
||
class="font-mono p-4 md:p-8 min-h-screen flex flex-col items-center text-white relative overflow-x-hidden"
|
||
>
|
||
<!-- 纯 CSS 背景动画 -->
|
||
<div class="vapor-sun"></div>
|
||
<div class="vapor-grid"></div>
|
||
|
||
<!-- 主窗口 -->
|
||
<div class="max-w-[1600px] w-full flex flex-col gap-6 z-10">
|
||
<!-- 标题区 -->
|
||
<header class="text-center mb-4">
|
||
<h1
|
||
class="font-vapor text-5xl md:text-7xl tracking-widest text-transparent bg-clip-text bg-gradient-to-b from-vapor-cyan to-vapor-purple drop-shadow-[0_0_15px_rgba(1,205,254,0.8)]"
|
||
style="-webkit-text-stroke: 1px #fff"
|
||
>
|
||
V A P O R _ D I F F
|
||
</h1>
|
||
<p
|
||
class="font-vapor text-vapor-pink mt-2 tracking-[0.5em] text-sm drop-shadow-[0_0_8px_#ff71ce]"
|
||
>
|
||
A E S T H E T I C S // 1 9 8 4
|
||
</p>
|
||
</header>
|
||
|
||
<!-- 双栏输入区 -->
|
||
<div class="flex flex-col lg:flex-row gap-6">
|
||
<!-- 左侧面板 -->
|
||
<div class="flex-1 flex flex-col vapor-panel rounded-xl">
|
||
<div
|
||
class="flex items-center justify-between px-4 py-3 border-b-2 border-vapor-purple bg-vapor-bg/50"
|
||
>
|
||
<label
|
||
class="font-vapor text-vapor-cyan tracking-widest flex items-center gap-2 drop-shadow-[0_0_5px_#01cdfe]"
|
||
>
|
||
[ CASSETTE_A ]
|
||
</label>
|
||
<button
|
||
id="clearLeftBtn"
|
||
class="font-vapor text-xs text-vapor-purple hover:text-vapor-pink transition-colors tracking-widest"
|
||
>
|
||
EJECT
|
||
</button>
|
||
</div>
|
||
<textarea
|
||
id="leftTextarea"
|
||
class="vapor-input w-full flex-1 min-h-[260px] h-[300px] p-5 text-[15px] leading-relaxed"
|
||
spellcheck="false"
|
||
>
|
||
function playSynthwave() {
|
||
console.log("Loading VHS tape...");
|
||
return "Nostalgia";
|
||
}</textarea
|
||
>
|
||
</div>
|
||
|
||
<!-- 右侧面板 -->
|
||
<div class="flex-1 flex flex-col vapor-panel rounded-xl">
|
||
<div
|
||
class="flex items-center justify-between px-4 py-3 border-b-2 border-vapor-purple bg-vapor-bg/50"
|
||
>
|
||
<label
|
||
class="font-vapor text-vapor-pink tracking-widest flex items-center gap-2 drop-shadow-[0_0_5px_#ff71ce]"
|
||
>
|
||
[ CASSETTE_B ]
|
||
</label>
|
||
<button
|
||
id="clearRightBtn"
|
||
class="font-vapor text-xs text-vapor-purple hover:text-vapor-cyan transition-colors tracking-widest"
|
||
>
|
||
EJECT
|
||
</button>
|
||
</div>
|
||
<textarea
|
||
id="rightTextarea"
|
||
class="vapor-input w-full flex-1 min-h-[260px] h-[300px] p-5 text-[15px] leading-relaxed"
|
||
spellcheck="false"
|
||
>
|
||
function playSynthwave() {
|
||
console.log("Loading LaserDisc...");
|
||
enableNeonGlow();
|
||
return "A E S T H E T I C S";
|
||
}</textarea
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 工具栏 -->
|
||
<div
|
||
class="vapor-panel rounded-xl p-4 flex flex-wrap items-center justify-between gap-4"
|
||
>
|
||
<div class="flex flex-wrap items-center gap-6">
|
||
<!-- 语言选择 -->
|
||
<div class="flex items-center gap-3">
|
||
<label
|
||
class="font-vapor text-sm tracking-widest text-vapor-yellow drop-shadow-[0_0_5px_#fffb96]"
|
||
>FORMAT:</label
|
||
>
|
||
<select
|
||
id="languageSelect"
|
||
class="bg-vapor-bg border-2 border-vapor-purple text-vapor-cyan px-3 py-1.5 font-vapor text-sm outline-none cursor-pointer hover:border-vapor-cyan focus:border-vapor-cyan focus:shadow-[0_0_10px_#01cdfe] transition-all appearance-none"
|
||
>
|
||
<option value="javascript" selected>JS_WAVE</option>
|
||
<option value="typescript">TS_WAVE</option>
|
||
<option value="html">HTML_84</option>
|
||
<option value="css">CSS_NEON</option>
|
||
<option value="json">JSON_DATA</option>
|
||
<option value="python">PY_SYNTH</option>
|
||
<option value="plaintext">RAW_TEXT</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 视图切换 -->
|
||
<div
|
||
class="flex items-center border-2 border-vapor-purple p-1 rounded-md bg-vapor-bg/50"
|
||
id="viewToggle"
|
||
>
|
||
<button
|
||
class="view-option active rounded-sm"
|
||
data-view="side-by-side"
|
||
>
|
||
STEREO
|
||
</button>
|
||
<button class="view-option rounded-sm" data-view="line-by-line">
|
||
MONO
|
||
</button>
|
||
</div>
|
||
|
||
<div data-style-switcher></div>
|
||
</div>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="flex gap-4 flex-wrap">
|
||
<button id="swapBtn" class="vapor-btn vapor-btn-cyan rounded-md">
|
||
REMIX
|
||
</button>
|
||
<button id="exampleBtn" class="vapor-btn vapor-btn-cyan rounded-md">
|
||
DEMO
|
||
</button>
|
||
<button
|
||
id="compareBtn"
|
||
class="vapor-btn vapor-btn-pink rounded-md bg-vapor-pink/10"
|
||
>
|
||
SYNC_TRACKS
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 差异结果区域 -->
|
||
<div class="vapor-panel rounded-xl flex flex-col">
|
||
<div
|
||
class="px-5 py-3 border-b-2 border-vapor-purple bg-vapor-bg/50 flex items-center justify-between"
|
||
>
|
||
<span
|
||
class="font-vapor tracking-widest text-vapor-yellow drop-shadow-[0_0_5px_#fffb96]"
|
||
>> PLAYBACK_RESULT</span
|
||
>
|
||
<span id="diffStats" class="font-vapor text-sm tracking-wider"></span>
|
||
</div>
|
||
<div
|
||
id="diff-output"
|
||
class="overflow-x-auto min-h-[150px] bg-vapor-bg/80"
|
||
>
|
||
<div
|
||
class="p-12 text-center font-vapor text-vapor-purple tracking-widest text-xl animate-pulse"
|
||
>
|
||
PRESS [ SYNC_TRACKS ] TO PLAY...
|
||
</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: "synthwave",
|
||
currentThemePath: "synthwave/index.html",
|
||
switcherLabel: "SKIN:",
|
||
switcherAriaLabel: "Switch synthwave skin",
|
||
fileLabels: {
|
||
left: "CASSETTE_A",
|
||
right: "CASSETTE_B",
|
||
},
|
||
example: {
|
||
left: `function playSynthwave() {\n console.log("Loading VHS tape...");\n return "Nostalgia";\n}`,
|
||
right: `function playSynthwave() {\n console.log("Loading LaserDisc...");\n enableNeonGlow();\n return "A E S T H E T I C S";\n}`,
|
||
language: "javascript",
|
||
},
|
||
messages: {
|
||
generateError: (error) =>
|
||
`<div class="p-12 text-center font-vapor text-vapor-pink tracking-widest text-xl">GLITCH_ERROR: ${error.message}</div>`,
|
||
renderError: () =>
|
||
'<div class="p-12 text-center font-vapor text-vapor-pink tracking-widest text-xl">TAPE_JAMMED</div>',
|
||
blankResult:
|
||
'<div class="p-12 text-center font-vapor text-vapor-cyan tracking-widest text-xl">TRACKS_ARE_IDENTICAL</div>',
|
||
blankStats: "<span class='text-vapor-cyan'>SYNCED</span>",
|
||
generateErrorStats: "ERR",
|
||
renderErrorStats: "ERR",
|
||
},
|
||
formatStats: ({ added, deleted, identical }) => {
|
||
if (identical) {
|
||
return "<span class='text-vapor-cyan drop-shadow-[0_0_5px_#01cdfe]'>VIBES_SYNCED</span>";
|
||
}
|
||
return `<span class='text-vapor-cyan drop-shadow-[0_0_5px_#01cdfe]'>+${added} UPLOADED</span> <span class='text-vapor-purple mx-2'>|</span> <span class='text-vapor-pink drop-shadow-[0_0_5px_#ff71ce]'>-${deleted} ERASED</span>`;
|
||
},
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|