🎨 优化扩展模块,完成ai接入和对话功能

This commit is contained in:
2026-02-12 23:12:28 +08:00
parent 4e611d3a5e
commit 572f3aa15b
779 changed files with 194400 additions and 3136 deletions

299
docs/demo.html Normal file
View File

@@ -0,0 +1,299 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
/* --- 沉浸式怪谈配色方案 --- */
:root {
--paper-bg: #e8e4d8; /* 陈旧纸张色 */
--ink-black: #1a1a1a; /* 墨色 */
--ghost-fire: #78ffc6; /* 磷火青 */
--seal-red: #8f2626; /* 朱砂红 */
--seal-red-glow: #ff4d4d; /* 鲜血红光 */
--font-main: "SimSun", "Songti SC", "Noto Serif SC", serif;
}
body {
margin: 0; padding: 0;
background: transparent;
font-family: var(--font-main);
color: var(--paper-bg);
display: flex; justify-content: center; align-items: center;
overflow: hidden;
user-select: none; /* 防止划动时选中文本 */
}
/* --- 主容器:灵视窗口 --- */
.occult-frame {
position: relative;
width: 100%; max-width: 520px;
background: linear-gradient(180deg, #0f1012 0%, #050505 100%);
border-radius: 4px;
box-shadow: 0 0 20px rgba(0,0,0,0.9), 0 0 0 1px #333;
overflow: hidden;
}
/* 顶部装饰:注连绳/结界风格 */
.frame-header {
height: 6px;
background: repeating-linear-gradient(
45deg,
var(--ink-black),
var(--ink-black) 10px,
var(--seal-red) 10px,
var(--seal-red) 20px
);
border-bottom: 1px solid #000;
}
/* --- 滑动轨道 --- */
.slider-window {
overflow: hidden;
position: relative;
}
.slider-track {
display: flex;
width: 100%;
transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1);
}
/* --- 卡片通用样式 --- */
.scenario-card {
flex: 0 0 100%;
box-sizing: border-box;
padding: 30px 25px 40px 25px;
position: relative;
opacity: 0.3;
transform: scale(0.96);
transition: all 0.5s ease;
filter: grayscale(80%);
}
.scenario-card.active {
opacity: 1;
transform: scale(1);
filter: grayscale(0%);
}
/* 背景装饰字 */
.bg-kanji {
position: absolute; top: 10px; right: 10px;
font-size: 120px; font-weight: bold;
opacity: 0.03; line-height: 1;
pointer-events: none; z-index: 0;
font-family: "SimSun", serif;
}
/* 标题组 */
.title-group {
position: relative; z-index: 2;
border-left: 3px solid var(--ghost-fire);
padding-left: 15px; margin-bottom: 20px;
}
.sub-title {
font-size: 12px; letter-spacing: 2px;
color: rgba(255,255,255,0.5);
text-transform: uppercase;
}
.main-title {
font-size: 32px; font-weight: bold;
margin-top: 4px;
text-shadow: 0 2px 10px rgba(0,0,0,0.8);
letter-spacing: 3px;
}
/* 正文区域 */
.context-box {
position: relative; z-index: 2;
font-size: 14px; line-height: 1.8;
color: #ccc;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.1);
padding: 15px;
border-radius: 2px;
}
/* 引导提示 */
.guide-box {
margin-top: 20px;
font-size: 12px;
color: #888;
font-style: italic;
text-align: center;
border-top: 1px solid rgba(255,255,255,0.1);
padding-top: 10px;
}
/* --- 风格差异化 --- */
/* 场景一:日常/入部 */
.scenario-card.intro .main-title { color: var(--paper-bg); }
.scenario-card.intro .title-group { border-color: #aaa; }
/* 场景二:怪谈/深渊 */
.scenario-card.horror .main-title {
color: var(--seal-red);
text-shadow: 0 0 10px rgba(143, 38, 38, 0.6);
}
.scenario-card.horror .title-group { border-color: var(--seal-red); }
.scenario-card.horror .bg-kanji { color: var(--seal-red-glow); opacity: 0.08; }
.scenario-card.horror .context-box {
border-color: rgba(143, 38, 38, 0.3);
background: linear-gradient(180deg, rgba(20,0,0,0.4) 0%, rgba(40,10,10,0.6) 100%);
}
/* --- 底部导航 --- */
.nav-indicator {
display: flex; justify-content: center; gap: 8px;
padding-bottom: 15px;
position: relative; z-index: 10;
}
.dot {
width: 40px; height: 3px; background: #333;
transition: all 0.3s;
}
.dot.active { background: var(--ghost-fire); box-shadow: 0 0 5px var(--ghost-fire); }
.dot.active-red { background: var(--seal-red-glow); box-shadow: 0 0 5px var(--seal-red-glow); }
/* 呼吸动画 */
@keyframes breathe { 0%,100%{opacity:0.6;} 50%{opacity:1;} }
.guide-blink { animation: breathe 3s infinite ease-in-out; }
</style>
</head>
<body>
<div class="occult-frame">
<div class="frame-header"></div>
<div class="slider-window">
<div class="slider-track" id="track">
<!-- 场景一初次访问 () -->
<div class="scenario-card intro active">
<div class="bg-kanji">日常</div>
<div class="title-group">
<div class="sub-title">SCENARIO_01</div>
<div class="main-title">旧校舍的访客</div>
</div>
<div class="context-box">
<p>热闹非凡的社团招新日躲避喧闹的你在老槐树下见到了奇特的少女</p>
<p>印着怪谈社三个字的手绘传单递到手中时命运的齿轮已经开始转动</p>
</div>
<div class="guide-box guide-blink">
>>> 适合初次体验剧情的新人部员 <<< <br>
请手动输入以此情境为开头的行动
</div>
</div>
<!-- 场景二深渊重临 () -->
<div class="scenario-card horror">
<div class="bg-kanji">怪谈</div>
<div class="title-group">
<div class="sub-title">SCENARIO_02</div>
<div class="main-title">百物语之夜</div>
</div>
<div class="context-box">
<p>不需要多余的寒暄青行灯的火光在黑暗中幽微地摇曳</p>
<p>作为已经缔结契约的共犯你熟练地拉开椅子白川绮罗香正等待着你准备开启下一个怪谈的封印</p>
</div>
<div class="guide-box guide-blink">
>>> 适合跳过引导的资深调查员 <<< <br>
请直接询问今天的怪谈是什么
</div>
</div>
</div>
</div>
<div class="nav-indicator">
<div class="dot active" id="dot0"></div>
<div class="dot" id="dot1"></div>
</div>
</div>
<script>
const track = document.getElementById('track');
const cards = document.querySelectorAll('.scenario-card');
const dots = [document.getElementById('dot0'), document.getElementById('dot1')];
let currentIndex = 0;
// 更新视图状态
function updateSlide(index) {
currentIndex = index;
// 移动轨道
track.style.transform = `translateX(-${index * 100}%)`;
// 卡片激活状态
cards.forEach((c, i) => {
c.classList.toggle('active', i === index);
});
// 底部指示条
dots.forEach((d, i) => {
d.className = 'dot'; // 重置
if (i === index) {
// 如果是第二页(index 1),用红色样式
d.classList.add(index === 1 ? 'active-red' : 'active');
}
});
}
// --- 触摸/鼠标滑动逻辑 ---
let startX = 0;
let isDragging = false;
// 触摸事件
track.addEventListener('touchstart', e => {
startX = e.touches[0].clientX;
isDragging = true;
}, {passive: true});
track.addEventListener('touchend', e => {
if (!isDragging) return;
handleSwipe(e.changedTouches[0].clientX);
isDragging = false;
});
// 鼠标事件 (兼容PC)
track.addEventListener('mousedown', e => {
startX = e.clientX;
isDragging = true;
});
document.addEventListener('mouseup', e => {
if (!isDragging) return;
handleSwipe(e.clientX);
isDragging = false;
});
// 处理滑动方向
function handleSwipe(endX) {
const diff = endX - startX;
const threshold = 50; // 滑动阈值
if (Math.abs(diff) > threshold) {
if (diff > 0 && currentIndex > 0) {
// 向右滑 -> 上一页
updateSlide(0);
} else if (diff < 0 && currentIndex < 1) {
// 向左滑 -> 下一页
updateSlide(1);
}
}
}
// 点击指示器切换
dots.forEach((dot, index) => {
dot.addEventListener('click', () => updateSlide(index));
});
// 初始化
updateSlide(0);
</script>
</body>
</html>