我真服了gemini了,怎么这么
b啊,deepseek+gemini两大模型齐心齐力,一起把我的心脏病吓出来了,啥也不说,直接上源代码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>雨夜来访 · 悬疑文字冒险</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap');
:root {
--bg-color: #050505;
--text-color: #d4c3a5;
--blood-red: #8a0303;
--ui-border: #2a1b1b;
}
* {
box-sizing: border-box;
font-family: 'Noto Serif SC', 'Times New Roman', serif;
user-select: none;
}
body {
background: var(--bg-color);
min-height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
color: var(--text-color);
overflow: hidden;
/* 防止特效导致滚动条闪烁 */
transition: background 1s ease;
}
/* 屏幕扫描线滤镜 */
.crt-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
background-size: 100% 2px, 3px 100%;
pointer-events: none;
z-index: 998;
opacity: 0.6;
}
/* 闪电特效 */
.lightning {
animation: flash 8s infinite;
}
@keyframes flash {
0%,
95%,
98%,
100% {
box-shadow: 0 0 30px rgba(74, 0, 0, 0.3);
background: #111;
}
96% {
box-shadow: 0 0 100px rgba(255, 255, 255, 0.8);
background: #333;
}
97% {
box-shadow: 0 0 30px rgba(74, 0, 0, 0.3);
background: #111;
}
99% {
box-shadow: 0 0 80px rgba(255, 255, 255, 0.5);
background: #222;
}
}
/* 低理智幻觉特效 */
.insane-mode {
animation: heartbeat 2s infinite;
text-shadow: 2px 0px 3px rgba(255, 0, 0, 0.5), -2px 0px 3px rgba(0, 0, 255, 0.5);
}
@keyframes heartbeat {
0% {
filter: blur(0px) contrast(1);
}
50% {
filter: blur(1px) contrast(1.2) drop-shadow(0 0 10px var(--blood-red));
}
100% {
filter: blur(0px) contrast(1);
}
}
.game-container {
max-width: 800px;
width: 100%;
background: #111;
border: 1px solid var(--ui-border);
padding: 30px;
position: relative;
z-index: 10;
transition: all 0.5s;
}
/* UI 头栏 */
.header-stats {
display: flex;
justify-content: space-between;
border-bottom: 1px dashed var(--blood-red);
padding-bottom: 15px;
margin-bottom: 25px;
font-size: 1.1rem;
letter-spacing: 2px;
}
.stat span {
font-weight: bold;
color: #fff;
}
#sanityVal {
transition: color 0.3s;
}
/* 线索区 */
.clues {
font-size: 0.85rem;
color: #888;
margin-bottom: 20px;
min-height: 30px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.clue-tag {
background: #1a0a0a;
border: 1px solid #4a1a1a;
padding: 3px 8px;
border-radius: 2px;
color: #c7a9a9;
}
/* 剧情文本区 */
.scene-box {
font-size: 1.15rem;
line-height: 1.8;
min-height: 280px;
margin-bottom: 30px;
white-space: pre-wrap;
color: #ccc;
text-shadow: 0 1px 2px #000;
}
/* 选项按钮 */
.options-area {
display: flex;
flex-direction: column;
gap: 15px;
}
.option-btn {
background: transparent;
border: 1px solid #444;
color: var(--text-color);
padding: 12px 20px;
font-size: 1.05rem;
text-align: left;
cursor: pointer;
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.option-btn::before {
content: "▶ ";
color: var(--blood-red);
opacity: 0;
transition: opacity 0.3s;
}
.option-btn:hover {
border-color: var(--blood-red);
background: rgba(138, 3, 3, 0.1);
padding-left: 25px;
}
.option-btn:hover::before {
opacity: 1;
}
/* 独立结局遮罩页(灵魂) */
#endingScreen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: #000;
z-index: 1000;
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
opacity: 0;
transition: opacity 2s ease-in;
}
#endingText {
max-width: 800px;
font-size: 1.3rem;
line-height: 2;
color: #fff;
text-align: justify;
white-space: pre-wrap;
}
.ending-title {
font-size: 2.5rem;
color: var(--blood-red);
margin-bottom: 30px;
letter-spacing: 5px;
opacity: 0;
text-shadow: 0 0 10px red;
transition: opacity 2s 1s;
/* 延迟显示 */
}
.restart-btn {
margin-top: 50px;
background: none;
border: 1px solid var(--blood-red);
color: var(--blood-red);
padding: 10px 30px;
cursor: pointer;
font-size: 1rem;
opacity: 0;
transition: all 0.3s;
}
.restart-btn:hover {
background: var(--blood-red);
color: #000;
}
</style>
</head>
<body>
<div class="crt-overlay"></div>
<div class="game-container lightning" id="gameContainer">
<div class="header-stats">
<div class="stat">理智 🧠 <span id="sanityVal">100</span></div>
<div class="stat">信任 🤝 <span id="trustVal">0</span></div>
<div class="stat">线索 📜 <span id="clueCount">0</span></div>
</div>
<div class="clues" id="clueList"></div>
<div class="scene-box" id="sceneDesc"></div>
<div class="options-area" id="optionsContainer"></div>
</div>
<div id="endingScreen">
<div class="ending-title" id="endingTitle"></div>
<div id="endingText"></div>
<button class="restart-btn" id="restartBtn" onclick="location.reload()">重新坠入雨夜</button>
</div>
<script>
(function () {
let state = {
sanity: 100,
trust: 0,
money: 0, // 新增金钱系统,补全购买绳索的逻辑闭环
clues: [],
hasDiary: false,
hasFireReport: false,
hasJidianLetter: false,
hasKey: false,
currentScene: 'OPENING',
};
function addClue(clueName) {
if (!state.clues.includes(clueName)) {
state.clues.push(clueName);
refreshUI();
}
}
function modifySanity(val) {
state.sanity = Math.max(0, state.sanity + val);
const sanDOM = document.getElementById('sanityVal');
sanDOM.innerText = state.sanity;
// 视觉压迫特效
if (state.sanity < 30) {
sanDOM.style.color = "red";
document.body.classList.add('insane-mode');
} else {
sanDOM.style.color = "#fff";
document.body.classList.remove('insane-mode');
}
}
function refreshUI() {
document.getElementById('sanityVal').innerText = state.sanity;
document.getElementById('trustVal').innerText = state.trust;
document.getElementById('clueCount').innerText = state.clues.length;
const clueDOM = document.getElementById('clueList');
clueDOM.innerHTML = state.clues.length ? state.clues.map(c => `<span class="clue-tag">${c}</span>`).join('') : '<span style="color:#555">你的大脑一片空白...</span>';
renderScene();
}
// --- 核心剧本重构 ---
const scenes = {
OPENING: {
desc: `[雨夜。昏黄的灯光从罩着绿纱的吊灯垂下,照着这间凌乱的侦探事务所。窗户玻璃上淌着水痕,街对面的霓虹招牌一明一灭,将红光短暂地投在屋内。]\n\n[你坐在转椅里。指尖夹着一支快燃尽的香烟。收音机里传来断断续续的沪剧唱腔,混着窗外的雨声。]`,
options: [
{ text: '掐灭烟', action: () => proceed('DOORBELL') },
{ text: '环顾四周', action: () => { addClue('霓虹灯节奏:闪三停一'); proceed('DOORBELL'); } },
{ text: '回想今日', action: () => { modifySanity(-2); proceed('DOORBELL'); } },
{ text: '查看烟灰缸', action: () => { addClue('医生的警告'); proceed('DOORBELL'); } }
]
},
DOORBELL: {
desc: `[门铃响起。叮铃。在雨声中显得格外刺耳。你没有动。叮铃。又是两声,短促而固执。]`,
options: [
{ text: '立即起身开门', action: () => proceed('MEET_WOMAN') },
{ text: '隔着门问“谁?”', action: () => { state.trust -= 1; proceed('MEET_WOMAN'); } }
]
},
MEET_WOMAN: {
desc: `[你打开了门。楼道昏暗的灯光下,站着一个女人。黑色旗袍湿漉漉地贴在身上。]\n\n“请问……您是沈默先生吗?”她的声音带着颤抖,“他们说,只有您会接那种没人愿意碰的案子。”\n\n[闪电划过,将她的影子猛地投在墙上。影子比真人长出一截,扭曲地延伸到天花板。你眨了眨眼——再看时,影子已经恢复正常。]`,
actionPre: () => modifySanity(-3),
options: [
{ text: '侧身让路:“进来谈。”', action: () => { state.trust += 5; proceed('WOMAN_INSIDE'); } },
{ text: '盯着她的眼睛:“什么案子?”', action: () => { addClue('闪避的目光'); proceed('WOMAN_INSIDE'); } }
]
},
WOMAN_INSIDE: {
desc: `[她迈进门槛,递给你一张照片。]\n“我丈夫失踪了。三天前。他叫程继先,圣约翰大学历史讲师。巡捕房说是跟舞女私奔。但他走的那天早上,连书房的门都特意锁了……他从来不锁书房的门。”`,
actionPre: () => addClue('程继先的照片'),
options: [
{ text: '问:“书房里有什么?”', action: () => { modifySanity(-10); proceed('ASK_STUDY') } },
{ text: '沉默,等她继续说', action: () => proceed('ASK_STUDY') }
]
},
ASK_STUDY: {
desc: `“门锁着,钥匙他带走了。但我找到了这个。”\n[她拿出一把黄铜钥匙和一封泛黄的信。钥匙柄上烧烙着:0713。信封上写着三个字:不可拆。]`,
options: [
{
text: '打开信封', action: () => {
addClue('继业的信(藏书楼,不可看/听/说)');
state.hasJidianLetter = true;
proceed('LETTER_OPENED');
}
},
{ text: '不打开,问她:“你为什么找我?”', action: () => proceed('WHY_ME') }
]
},
LETTER_OPENED: {
desc: `[你抽出信纸:“继先吾弟:此事不可为外人道。藏书楼之事,须在月圆之前了结。切记:不可看,不可听,不可说。兄 继业 民国十六年七月十三”]\n\n她脸色苍白:“继业……他哥哥,那年失踪了……”`,
options: [
{ text: '问她:“你为什么找我?”', action: () => proceed('WHY_ME') }
]
},
WHY_ME: {
desc: `“去年法租界的画中人案子,我知道是您查出来的。我需要一个不怕‘那个’的人。”\n[她恳求你今晚就去程家查看书房。]`,
options: [
{ text: '答应,即刻前往', action: () => proceed('CHENG_HOUSE') },
{ text: '答应,但要求先收定金 (获得5大洋)', action: () => { state.trust -= 10; state.money = 5; addClue('5块大洋'); proceed('PREPARE_STORE'); } },
{ text: '拒绝,让她找别人', action: () => proceed('DEATH_LUNG') }
]
},
DEATH_LUNG: {
desc: `[你继续坐在事务所里,听着雨声。三个月后,你死于肺病。]`,
options: [
{ text: '(重新开始)', action: () => location.reload() }
] // 将在代码逻辑中拦截并触发结局机制,但这里保留文本作为备用
},
PREPARE_STORE: {
desc: `[出门前,你路过了楼下的杂货铺。你手里有她给的5块大洋。]`,
options: [
{ text: '购买打桩绳索 (-5大洋)', action: () => { addClue('绳索'); proceed('CHENG_HOUSE'); } },
{ text: '什么都不买,直接去程宅', action: () => proceed('CHENG_HOUSE') }
]
},
CHENG_HOUSE: {
desc: `[你们来到程家二楼书房。书房不大,一张书桌,书架上有一排书脊朝内的旧书,地上的老式保险柜有被拖拽的划痕。]`,
options: [
{
text: '调查书桌日记', action: () => {
if (!state.hasDiary) {
modifySanity(-8); addClue('日记(那东西在窗外)'); state.hasDiary = true;
}
proceed('CHENG_HOUSE_2');
}
},
{ text: '询问藏书楼的位置', action: () => proceed('ASK_LIBRARY') }
]
},
CHENG_HOUSE_2: {
desc: `[你放下日记,书房里弥漫着一股陈旧的纸张气味。地上的划痕和那排倒置的旧书显得格格不入。]`,
options: [
{ text: '询问藏书楼的位置', action: () => proceed('ASK_LIBRARY') }
]
},
ASK_LIBRARY: {
desc: `“沪江大学的藏书楼……那是二十年前就关闭的地方。民国十六年大火,烧死了七个学生。”\n[民国十六年。正是继业写信的那一年。]`,
options: [
{ text: '立刻前往藏书楼', action: () => proceed('LIBRARY_EXT') },
{ text: '让程婉宜留在这里,你独自前往', action: () => { state.trust += 5; proceed('LIBRARY_EXT'); } }
]
},
LIBRARY_EXT: {
desc: `[藏书楼外墙爬满枯死藤蔓。铁门紧锁。月亮从云层后露出。你注意到,三楼的窗户……有一扇是开着的。]`,
actionPre: () => modifySanity(-5),
options: [
{ text: '用黄铜钥匙0713开门', action: () => proceed('LIBRARY_LOBBY') },
{
text: '利用绳索从三楼开着的窗户爬入', action: () => {
if (state.clues.includes('绳索')) {
proceed('FLOOR_3_DIRECT');
} else {
alert('墙壁太滑,你没有攀爬工具,险些摔断脖子!');
modifySanity(-10);
proceed('LIBRARY_EXT');
}
}
}
]
},
LIBRARY_LOBBY: {
desc: `[钥匙转动,门开了。\n楼内弥漫焦臭味。大厅中央的镜子里……你看到一个人影。你猛地回头,身后无人。再看镜子,人影消失了。]`,
actionPre: () => modifySanity(-5),
options: [
{ text: '先检查一楼废墟', action: () => proceed('FLOOR_1') },
{ text: '直接上二楼', action: () => proceed('FLOOR_2') }
]
},
FLOOR_1: {
desc: `[一楼阅览室满地灰烬。你捡起一片烧焦的书页,上面写着英文:“仪式必须在满月前完成。它们在等待。”]`,
actionPre: () => { modifySanity(-10); addClue('残页(它们在等待)'); },
options: [
{ text: '前往二楼', action: () => proceed('FLOOR_2') }
]
},
FLOOR_2: {
desc: `[二楼藏书室角落有个完好的铁皮柜。里面是《民国十六年火灾调查报告》。死者名单第三个名字赫然是:程继业。\n死人,怎么会给活着的弟弟写信?]`,
actionPre: () => { modifySanity(-8); state.hasFireReport = true; addClue('报告(继业已死)'); },
options: [
{ text: '深吸一口气,上三楼', action: () => proceed('FLOOR_3') }
]
},
FLOOR_3: {
desc: `[三楼。走廊尽头那扇窗户开着。路过第二扇紧闭的门时,你停下了。\n\n门缝里,一只眼睛也在盯着你。]`,
actionPre: () => modifySanity(-10),
options: [
{ text: '推开那扇门', action: () => { triggerEnding('DOOR'); } },
{ text: '无视它,走向走廊尽头的窗户', action: () => { triggerEnding('WINDOW'); } },
{
text: '转身逃跑', action: () => {
if (state.sanity < 40) triggerEnding('ESCAPE');
else proceed('LIBRARY_LOBBY'); // 理智够高还能退回大厅
}
}
]
},
FLOOR_3_DIRECT: {
desc: `[你顺着绳索翻入三楼走廊。惨白的月光下,走廊里的一扇门缝处,似乎有什么东西正在蠕动...]`,
options: [
{ text: '靠近那扇门', action: () => proceed('FLOOR_3') }
]
}
};
// --- 结局剧本(完美还原原定字数与意境) ---
const endings = {
TRUE: {
title: "结局一:沉默的代价",
text: "你拼凑出真相:\n\n民国十六年,程继业和六名学生,在藏书楼进行一场禁忌的仪式——试图召唤“门后之物”。仪式失败,七人死亡。但程继业在死前,用自己的血写下了那封信,寄给弟弟。\n\n二十年来,程继先一直在研究哥哥留下的遗物。他发现了仪式的真相,也发现了——“它们”从未离开。它们一直在等。等月圆之夜,等一个“自愿者”完成仪式。\n\n三天前,程继先来到藏书楼。他决定完成哥哥未竟的事——不是为了召唤,而是为了关闭那扇门。\n\n你推开三楼最里面的门。月光从窗外照进来。程继先坐在椅子上,低着头,像是睡着了。他的手里握着一本烧焦一半的日记。最后一页写着:\n\n『我关上门了。代价是我自己。婉宜,对不起。沈先生,若您看到这些,请告诉她——不要来找我。永远不要。』\n\n你抬起头。程继先的“身体”开始风化,碎成粉末,散落在月光里。\n\n你完成了委托。但你知道,你永远不会告诉她真相。"
},
DOOR_THING: {
title: "结局二:永远的雨夜",
text: "你没有找到程继先。\n\n但你在三楼最里面的房间里,看到了那扇“门”。它不是普通的门。它嵌在墙上,由暗红色的木头制成,没有任何把手、任何缝隙。\n\n但它……在呼吸。\n\n你盯着它。它也“盯”着你。你听到门后传来声音。很多人的声音。他们在低语,在呼唤——呼唤你的名字。\n\n你转身就跑。一路冲出藏书楼,冲过程家的大门,冲回你的事务所。你锁上门,关上窗,蜷缩在沙发里。\n\n门外,雨又下起来了。\n\n雨声中,你听到——有人在敲门。\n\n一下。两下。三下。\n\n你不敢开门。"
},
MIRROR: {
title: "结局三:它来了",
text: "你在藏书楼的镜子里,看到了自己。\n\n但那个“你”在笑。\n\n极度的恐惧压垮了你,你甚至连尖叫都发不出,跌跌撞撞地逃出了那栋大楼。你回到了事务所,试图继续过你接私活、找猫狗的日子,试图把那一夜当成一场幻觉。\n\n但是,有些东西被打破了。\n\n每天早晨,你在盥洗室照镜子的时候,镜子里的人动作总会比你快上那么一瞬。\n\n直到今天早晨,你还没有牵扯嘴角。\n\n但他先对你笑了。"
},
MADNESS: {
title: "结局四:第七个",
text: "你无法承受看到的那些东西。你的理智彻底崩溃了。\n\n巡捕房在三天后找到了你。你蜷缩在藏书楼三楼冰冷的地板上,怀里紧紧抱着一个烧焦的洋娃娃。无论谁跟你说话,你的嘴里只是反复呢喃着:\n\n“不可看……不可听……不可说……”\n\n他们把你送进了宛平南路的精神病院。\n\n后来的每一个月圆之夜,你都会准时在午夜醒来,对着雪白的墙壁说话。值班的护士在病历上写着你“经常出现对空交谈的幻觉”。\n\n但护士们看不见。\n\n在你的对面,一直整整齐齐地坐着七个穿着民国十六年校服的人。他们在听你说话。"
},
ZERO: {
title: "结局五:门已开",
text: "理智值的清零,反而让你终于明白了那三个字的真正含义。\n\n不可看——但你看了。\n不可听——但你听了。\n不可说——但现在,你要说了。\n\n你的咽喉里发出不属于人类的怪异音节,你开口,念出了你在那封烧焦的信上看到的咒文。不是为了召唤它们,而是因为——“它们”需要一具现实中的皮囊,替它们在现世说出那个名字。\n\n你说出了那个名字。\n\n沉重的吱呀声响起。门,开了。\n\n第二天,人们在藏书楼发现了你。你安安静静地坐在三楼办公室的椅子上,面带微笑,瞳孔早已涣散。法医鉴定后确认,你的死亡时间……是七天前。\n\n可是,昨夜的你,明明还活着。"
},
NOTH: {
title: "死",
text: "你继续坐在事务所里,听着雨声。三个月后,你死于肺病。"
}
};
function triggerEnding(type) {
document.getElementById('gameContainer').style.display = 'none';
const scr = document.getElementById('endingScreen');
scr.style.display = 'flex';
// 强制重绘以触发淡入
setTimeout(() => { scr.style.opacity = 1; }, 50);
let endData;
// 核心判据:补全剧本要求的理智分层逻辑
if (type === 'WINDOW') {
endData = endings.MADNESS;
scr.style.background = "#0a0000"; // 微红底
}
else if (state.sanity >= 60 && state.hasDiary && state.hasFireReport && state.hasJidianLetter && type === 'DOOR' && state.trust >= 5) {
endData = endings.TRUE;
scr.style.background = "#050505"; // 黑色底
} else if (state.sanity >= 30 && type === 'DOOR') {
endData = endings.DOOR_THING;
scr.style.background = "#0a0000"; // 微红底
} else if (state.sanity > 0 && state.sanity < 30 && type === 'DOOR') {
endData = endings.MIRROR;
scr.style.background = "#1a1a1a";
}
else if (state.sanity <= 0 || type === 'ESCAPE') {
endData = endings.ZERO;
scr.style.background = "#3a0000"; // 猩红底
} else {
endData = endings.NOTH;
scr.style.background = "#5a0000"; // 紫红底
}
document.getElementById('endingTitle').innerText = endData.title;
document.getElementById('endingTitle').style.opacity = 1;
// sanDOM.style.color = "red";
document.body.classList.add('insane-mode');
// 打字机特效输出恐怖文本
const textDOM = document.getElementById('endingText');
textDOM.innerHTML = '';
let i = 0;
let txt = endData.text;
function typeWriter() {
if (i < txt.length) {
textDOM.innerHTML += txt.charAt(i) === '\n' ? '<br>' : txt.charAt(i);
i++;
// 根据标点停顿增加压抑感
let speed = 40;
let char = txt.charAt(i - 1);
if (char === '。' || char === '…') speed = 500;
if (char === ',') speed = 200;
setTimeout(typeWriter, speed);
} else {
document.getElementById('restartBtn').style.opacity = 1;
}
}
setTimeout(typeWriter, 2000); // 标题显示2秒后开始打字
}
function proceed(sceneId) {
if (sceneId === 'DEATH_LUNG') {
state.sanity = 100; // 强制走归零判定或单独处理
triggerEnding('DEATH');
return;
}
if (scenes[sceneId] && scenes[sceneId].actionPre) {
scenes[sceneId].actionPre();
}
// 绝望防线:随时监控理智崩溃
if (state.sanity <= 0 && sceneId !== 'OPENING' && !sceneId.includes('ENDING')) {
triggerEnding('MAD');
return;
}
state.currentScene = sceneId;
refreshUI();
}
function renderScene() {
let scene = scenes[state.currentScene];
if (!scene) return;
let desc = scene.desc;
if (state.sanity < 30) {
desc += '\n\n[你的视线边缘开始扭曲,耳畔传来窸窸窣窣的低语声……]';
}
document.getElementById('sceneDesc').innerText = desc;
let optionsHtml = '';
if (scene.options && scene.options.length > 0) {
scene.options.forEach((opt, idx) => {
optionsHtml += `<button class="option-btn" data-idx="${idx}">${opt.text}</button>`;
});
}
document.getElementById('optionsContainer').innerHTML = optionsHtml;
document.querySelectorAll('.option-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = e.target.getAttribute('data-idx');
if (scene.options[idx].action) {
scene.options[idx].action();
}
});
});
}
// 启动游戏
refreshUI();
})();
</script>
</body>
</html>
直接复制放文本文档里就好,改一下后缀双击就可体验窒息的感觉(ps:千万不要晚上玩!!!!)
管理大大补药删帖o(>﹏<)o,违规了自己会删的,极水贴,若好玩给个赞吧。
