Files
diff.tootaio.com/synthwave/index.html
2026-04-08 14:27:48 +08:00

618 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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=
@message_part::reasoning-1_2
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];
}
}
/* ==========================================
纯 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>
<!-- 操作按钮 -->
<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>
(function () {
"use strict";
const leftTextarea = document.getElementById("leftTextarea");
const rightTextarea = document.getElementById("rightTextarea");
const compareBtn = document.getElementById("compareBtn");
const swapBtn = document.getElementById("swapBtn");
const exampleBtn = document.getElementById("exampleBtn");
const clearLeftBtn = document.getElementById("clearLeftBtn");
const clearRightBtn = document.getElementById("clearRightBtn");
const languageSelect = document.getElementById("languageSelect");
const diffOutput = document.getElementById("diff-output");
const diffStats = document.getElementById("diffStats");
const viewOptions = document.querySelectorAll(".view-option");
let currentView = "side-by-side";
function getSelectedLanguage() {
let val = languageSelect.value;
// 映射回真实的语言名称
if (val === "javascript") return "javascript";
if (val === "typescript") return "typescript";
if (val === "html") return "html";
if (val === "css") return "css";
if (val === "json") return "json";
if (val === "python") return "python";
return "plaintext";
}
function getHighlightConfig() {
const lang = getSelectedLanguage();
if (lang === "plaintext") return false;
return { enabled: true, language: lang };
}
function generateUnifiedDiff(original, modified) {
return Diff.createTwoFilesPatch(
"CASSETTE_A",
"CASSETTE_B",
original || "",
modified || "",
"",
"",
{ context: 4 },
);
}
function renderDiff() {
const leftText = leftTextarea.value;
const rightText = rightTextarea.value;
let diffString;
try {
diffString = generateUnifiedDiff(leftText, rightText);
} catch (e) {
diffOutput.innerHTML = `<div class="p-12 text-center font-vapor text-vapor-pink tracking-widest text-xl">GLITCH_ERROR: ${e.message}</div>`;
diffStats.textContent = "ERR";
return;
}
const configuration = {
drawFileList: false,
matching: "lines",
outputFormat: currentView,
highlight: getHighlightConfig(),
renderNothingWhenEmpty: false,
};
let diffHtml = "";
try {
diffHtml = Diff2Html.html(diffString, configuration);
} catch (e) {
diffOutput.innerHTML = `<div class="p-12 text-center font-vapor text-vapor-pink tracking-widest text-xl">TAPE_JAMMED</div>`;
diffStats.textContent = "ERR";
return;
}
diffOutput.innerHTML = diffHtml;
const selectedLang = getSelectedLanguage();
if (selectedLang !== "plaintext") {
const codeBlocks = diffOutput.querySelectorAll("code");
if (codeBlocks.length > 0 && window.hljs) {
codeBlocks.forEach((block) => {
block.classList.forEach((cls) => {
if (cls.startsWith("language-")) block.classList.remove(cls);
});
block.classList.add(`language-${selectedLang}`);
if (block.dataset.highlighted) {
delete block.dataset.highlighted;
}
try {
hljs.highlightElement(block);
} catch (e) {}
});
}
}
const addedLines = diffOutput.querySelectorAll(".d2h-ins").length;
const deletedLines = diffOutput.querySelectorAll(".d2h-del").length;
if (addedLines === 0 && deletedLines === 0) {
diffStats.innerHTML =
"<span class='text-vapor-cyan drop-shadow-[0_0_5px_#01cdfe]'>VIBES_SYNCED</span>";
} else {
diffStats.innerHTML = `<span class='text-vapor-cyan drop-shadow-[0_0_5px_#01cdfe]'>+${addedLines} UPLOADED</span> <span class='text-vapor-purple mx-2'>|</span> <span class='text-vapor-pink drop-shadow-[0_0_5px_#ff71ce]'>-${deletedLines} ERASED</span>`;
}
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
diffOutput.innerHTML =
'<div class="p-12 text-center font-vapor text-vapor-cyan tracking-widest text-xl">TRACKS_ARE_IDENTICAL</div>';
diffStats.innerHTML = "<span class='text-vapor-cyan'>SYNCED</span>";
}
}
function setActiveView(view) {
currentView = view;
viewOptions.forEach((btn) => {
const val = btn.getAttribute("data-view");
if (val === view) {
btn.classList.add("active");
} else {
btn.classList.remove("active");
}
});
if (
diffOutput.querySelector(".d2h-wrapper") ||
diffOutput.children.length > 0
) {
renderDiff();
}
}
function loadExample() {
leftTextarea.value = `function playSynthwave() {\n console.log("Loading VHS tape...");\n return "Nostalgia";\n}`;
rightTextarea.value = `function playSynthwave() {\n console.log("Loading LaserDisc...");\n enableNeonGlow();\n return "A E S T H E T I C S";\n}`;
// 恢复下拉框的显示值
languageSelect.selectedIndex = 0;
renderDiff();
}
function swapTexts() {
const temp = leftTextarea.value;
leftTextarea.value = rightTextarea.value;
rightTextarea.value = temp;
renderDiff();
}
function clearLeft() {
leftTextarea.value = "";
renderDiff();
}
function clearRight() {
rightTextarea.value = "";
renderDiff();
}
compareBtn.addEventListener("click", renderDiff);
swapBtn.addEventListener("click", swapTexts);
exampleBtn.addEventListener("click", loadExample);
clearLeftBtn.addEventListener("click", clearLeft);
clearRightBtn.addEventListener("click", clearRight);
languageSelect.addEventListener("change", () => {
if (
diffOutput.querySelector(".d2h-wrapper") ||
diffOutput.children.length > 0
)
renderDiff();
});
viewOptions.forEach((btn) => {
btn.addEventListener("click", (e) => {
setActiveView(e.currentTarget.getAttribute("data-view"));
});
});
document.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
e.preventDefault();
renderDiff();
}
});
window.addEventListener("DOMContentLoaded", renderDiff);
})();
</script>
</body>
</html>