initial commit

This commit is contained in:
2026-04-08 14:27:48 +08:00
commit 90792b276e
7 changed files with 3821 additions and 0 deletions

617
synthwave/index.html Normal file
View File

@@ -0,0 +1,617 @@
<!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>