2110 lines
82 KiB
HTML
2110 lines
82 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<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+Sans+SC:wght@300;400;500;700&display=swap');
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Noto Sans SC', sans-serif;
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
min-height: 100vh;
|
|
color: #e8e8e8;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* 主容器 */
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
/* 顶部状态栏 */
|
|
.status-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 15px 25px;
|
|
background: rgba(255,255,255,0.05);
|
|
border-radius: 12px;
|
|
margin-bottom: 20px;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
}
|
|
|
|
.status-bar h1 {
|
|
font-size: 1.4em;
|
|
font-weight: 500;
|
|
color: #64ffda;
|
|
}
|
|
|
|
.phase-indicator {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.phase-dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: rgba(255,255,255,0.2);
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.phase-dot.active {
|
|
background: #64ffda;
|
|
box-shadow: 0 0 10px #64ffda;
|
|
}
|
|
|
|
.phase-dot.completed {
|
|
background: #4ade80;
|
|
}
|
|
|
|
/* 主要内容区 */
|
|
.main-content {
|
|
display: grid;
|
|
grid-template-columns: 280px 1fr 280px;
|
|
gap: 20px;
|
|
min-height: 600px;
|
|
}
|
|
|
|
/* 左侧面板 - 模块选择 */
|
|
.module-panel {
|
|
background: rgba(255,255,255,0.03);
|
|
border-radius: 16px;
|
|
padding: 20px;
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
}
|
|
|
|
.module-panel h2 {
|
|
font-size: 1em;
|
|
color: #94a3b8;
|
|
margin-bottom: 15px;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.module-btn {
|
|
width: 100%;
|
|
padding: 15px;
|
|
margin-bottom: 12px;
|
|
background: rgba(255,255,255,0.05);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
border-radius: 10px;
|
|
color: #e8e8e8;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
text-align: left;
|
|
font-size: 0.95em;
|
|
}
|
|
|
|
.module-btn:hover {
|
|
background: rgba(255,255,255,0.1);
|
|
border-color: rgba(100,255,218,0.3);
|
|
}
|
|
|
|
.module-btn.active {
|
|
background: rgba(100,255,218,0.15);
|
|
border-color: #64ffda;
|
|
}
|
|
|
|
.module-btn.locked {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.module-btn .status {
|
|
font-size: 0.8em;
|
|
color: #64ffda;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.module-btn .status.warning {
|
|
color: #fbbf24;
|
|
}
|
|
|
|
.module-btn .status.locked {
|
|
color: #ef4444;
|
|
}
|
|
|
|
/* 中央区域 - 游戏/内容区 */
|
|
.game-area {
|
|
background: rgba(0,0,0,0.3);
|
|
border-radius: 16px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
}
|
|
|
|
.game-canvas {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
}
|
|
|
|
/* 诊断界面 */
|
|
.diagnosis-view {
|
|
padding: 30px;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.diagnosis-title {
|
|
font-size: 1.3em;
|
|
color: #64ffda;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.diagnosis-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
|
|
.data-card {
|
|
background: rgba(255,255,255,0.05);
|
|
border-radius: 10px;
|
|
padding: 15px;
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
}
|
|
|
|
.data-card h3 {
|
|
font-size: 0.9em;
|
|
color: #94a3b8;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.data-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
}
|
|
|
|
.data-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.data-label {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.data-value {
|
|
color: #e8e8e8;
|
|
}
|
|
|
|
.data-value.normal {
|
|
color: #4ade80;
|
|
}
|
|
|
|
.data-value.warning {
|
|
color: #fbbf24;
|
|
}
|
|
|
|
.data-value.error {
|
|
color: #ef4444;
|
|
}
|
|
|
|
/* 波形图 */
|
|
.waveform-container {
|
|
height: 100px;
|
|
background: rgba(0,0,0,0.3);
|
|
border-radius: 8px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.waveform {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 60px;
|
|
transform: translateY(-50%);
|
|
}
|
|
|
|
/* 情绪对比选择器 */
|
|
.emotion-selector {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.emotion-option {
|
|
padding: 12px;
|
|
background: rgba(255,255,255,0.05);
|
|
border: 2px solid rgba(255,255,255,0.1);
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
text-align: center;
|
|
}
|
|
|
|
.emotion-option:hover {
|
|
background: rgba(255,255,255,0.1);
|
|
}
|
|
|
|
.emotion-option.selected {
|
|
border-color: #64ffda;
|
|
background: rgba(100,255,218,0.1);
|
|
}
|
|
|
|
.emotion-option .match {
|
|
font-size: 0.8em;
|
|
color: #64ffda;
|
|
margin-top: 5px;
|
|
display: none;
|
|
}
|
|
|
|
.emotion-option.selected .match {
|
|
display: block;
|
|
}
|
|
|
|
/* 操作按钮 */
|
|
.action-btn {
|
|
padding: 12px 24px;
|
|
background: linear-gradient(135deg, #64ffda 0%, #4ade80 100%);
|
|
border: none;
|
|
border-radius: 8px;
|
|
color: #1a1a2e;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 20px rgba(100,255,218,0.3);
|
|
}
|
|
|
|
.action-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
/* 右侧面板 - 信号分析 */
|
|
.pathway-panel {
|
|
background: rgba(255,255,255,0.03);
|
|
border-radius: 16px;
|
|
padding: 20px;
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
}
|
|
|
|
.pathway-panel h2 {
|
|
font-size: 1em;
|
|
color: #94a3b8;
|
|
margin-bottom: 15px;
|
|
font-weight: 400;
|
|
}
|
|
|
|
/* 拔河条设计 */
|
|
.tugofwar-container {
|
|
background: rgba(255,255,255,0.05);
|
|
border-radius: 12px;
|
|
padding: 20px 15px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.tugofwar-labels {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 12px;
|
|
font-size: 0.85em;
|
|
}
|
|
|
|
.tugofwar-label-left {
|
|
color: #4ade80;
|
|
}
|
|
|
|
.tugofwar-label-right {
|
|
color: #f87171;
|
|
}
|
|
|
|
.tugofwar-bar {
|
|
height: 12px;
|
|
background: linear-gradient(90deg,
|
|
rgba(74, 222, 128, 0.3) 0%,
|
|
rgba(255,255,255,0.1) 50%,
|
|
rgba(248, 113, 113, 0.3) 100%);
|
|
border-radius: 6px;
|
|
position: relative;
|
|
overflow: visible;
|
|
}
|
|
|
|
.tugofwar-indicator {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 24px;
|
|
height: 24px;
|
|
background: #fff;
|
|
border-radius: 50%;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
transition: left 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.tugofwar-indicator.winning-left {
|
|
background: linear-gradient(135deg, #4ade80, #22c55e);
|
|
box-shadow: 0 0 12px rgba(74, 222, 128, 0.5);
|
|
}
|
|
|
|
.tugofwar-indicator.winning-right {
|
|
background: linear-gradient(135deg, #f87171, #ef4444);
|
|
box-shadow: 0 0 12px rgba(248, 113, 113, 0.5);
|
|
}
|
|
|
|
.tugofwar-indicator.balanced {
|
|
background: linear-gradient(135deg, #fbbf24, #f59e0b);
|
|
}
|
|
|
|
.tugofwar-marks {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: 8px;
|
|
font-size: 0.7em;
|
|
color: #64748b;
|
|
}
|
|
|
|
.tugofwar-status {
|
|
text-align: center;
|
|
margin-top: 15px;
|
|
padding: 10px;
|
|
background: rgba(255,255,255,0.03);
|
|
border-radius: 8px;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
/* 未分析状态 */
|
|
.analysis-pending {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
color: #64748b;
|
|
}
|
|
|
|
.analysis-pending .icon {
|
|
font-size: 2em;
|
|
margin-bottom: 15px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
/* 分析动画 */
|
|
.analyzing {
|
|
text-align: center;
|
|
padding: 30px 20px;
|
|
}
|
|
|
|
.analyzing .spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 3px solid rgba(255,255,255,0.1);
|
|
border-top-color: #64ffda;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 15px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* 语言生成显示器 */
|
|
.language-display {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(0,0,0,0.7);
|
|
border: 1px solid rgba(100, 255, 218, 0.3);
|
|
border-radius: 8px;
|
|
padding: 12px 24px;
|
|
z-index: 15;
|
|
text-align: center;
|
|
min-width: 200px;
|
|
}
|
|
|
|
.display-label {
|
|
font-size: 0.75em;
|
|
color: #64748b;
|
|
margin-bottom: 6px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.display-text {
|
|
font-size: 1.4em;
|
|
color: #64ffda;
|
|
font-family: 'Courier New', monospace;
|
|
min-height: 1.5em;
|
|
}
|
|
|
|
.display-text.error {
|
|
color: #f87171;
|
|
}
|
|
|
|
.display-text.generating {
|
|
animation: textBlink 0.5s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes textBlink {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.6; }
|
|
}
|
|
|
|
|
|
/* 对话框 */
|
|
.dialog-box {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: linear-gradient(to top, rgba(0,0,0,0.95), rgba(0,0,0,0.8));
|
|
padding: 20px 30px;
|
|
border-top: 1px solid rgba(255,255,255,0.1);
|
|
transform: translateY(100%);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.dialog-box.show {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.dialog-speaker {
|
|
color: #64ffda;
|
|
font-size: 0.9em;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.dialog-text {
|
|
font-size: 1.1em;
|
|
line-height: 1.6;
|
|
max-width: 800px;
|
|
}
|
|
|
|
.dialog-continue {
|
|
position: absolute;
|
|
right: 30px;
|
|
bottom: 20px;
|
|
color: #64ffda;
|
|
font-size: 0.85em;
|
|
animation: blink 1s infinite;
|
|
}
|
|
|
|
@keyframes blink {
|
|
0%, 50% { opacity: 1; }
|
|
51%, 100% { opacity: 0.3; }
|
|
}
|
|
|
|
/* 粒子游戏区域 */
|
|
.particle-game {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
}
|
|
|
|
.particle {
|
|
position: absolute;
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.2em;
|
|
cursor: default;
|
|
transition: transform 0.1s;
|
|
user-select: none;
|
|
}
|
|
|
|
.particle.target {
|
|
background: radial-gradient(circle at 30% 30%, #ff6b6b, #c0392b);
|
|
box-shadow: 0 0 20px rgba(255,107,107,0.5);
|
|
}
|
|
|
|
.particle.non-target {
|
|
background: radial-gradient(circle at 30% 30%, #74b9ff, #0984e3);
|
|
box-shadow: 0 0 15px rgba(116,185,255,0.4);
|
|
}
|
|
|
|
.particle.constructing {
|
|
background: radial-gradient(circle at 30% 30%, #64ffda, #00b894);
|
|
box-shadow: 0 0 25px rgba(100,255,218,0.6);
|
|
animation: pulse 1s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); }
|
|
50% { transform: scale(1.05); }
|
|
}
|
|
|
|
/* 连接线 SVG */
|
|
.connections-svg {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.connection-line {
|
|
stroke: rgba(255,107,107,0.6);
|
|
stroke-width: 2;
|
|
fill: none;
|
|
}
|
|
|
|
/* 游戏UI覆盖层 */
|
|
.game-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
padding: 15px 20px;
|
|
background: linear-gradient(to bottom, rgba(0,0,0,0.5), transparent);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.game-instruction {
|
|
font-size: 0.9em;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.game-progress {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.progress-label {
|
|
font-size: 0.85em;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.progress-bar {
|
|
width: 150px;
|
|
height: 6px;
|
|
background: rgba(255,255,255,0.1);
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: #64ffda;
|
|
transition: width 0.3s;
|
|
}
|
|
|
|
/* 保护模式特殊UI */
|
|
.protection-mode .game-instruction {
|
|
color: #fbbf24;
|
|
}
|
|
|
|
.construction-progress {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
text-align: center;
|
|
}
|
|
|
|
.construction-text {
|
|
font-size: 1.5em;
|
|
color: #64ffda;
|
|
margin-bottom: 10px;
|
|
text-shadow: 0 0 20px rgba(100,255,218,0.5);
|
|
}
|
|
|
|
.construction-bar {
|
|
width: 300px;
|
|
height: 10px;
|
|
background: rgba(255,255,255,0.1);
|
|
border-radius: 5px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.construction-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #64ffda, #4ade80);
|
|
transition: width 0.3s;
|
|
}
|
|
|
|
/* 隐藏元素 */
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
/* 结果屏幕 */
|
|
.result-screen {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0,0,0,0.9);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.5s;
|
|
}
|
|
|
|
.result-screen.show {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.result-text {
|
|
font-size: 1.5em;
|
|
color: #64ffda;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.result-detail {
|
|
color: #94a3b8;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
/* 开场界面 */
|
|
.intro-screen {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
transition: opacity 0.5s;
|
|
}
|
|
|
|
.intro-screen.hidden {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.intro-title {
|
|
font-size: 2.5em;
|
|
color: #64ffda;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.intro-subtitle {
|
|
font-size: 1.2em;
|
|
color: #94a3b8;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.intro-btn {
|
|
padding: 15px 40px;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
/* 火山说话气泡 */
|
|
.huoshan-bubble {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
max-width: 250px;
|
|
padding: 15px;
|
|
background: rgba(255,107,107,0.15);
|
|
border: 1px solid rgba(255,107,107,0.3);
|
|
border-radius: 12px;
|
|
color: #ff6b6b;
|
|
font-size: 0.9em;
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.huoshan-bubble.show {
|
|
opacity: 1;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- 开场界面 -->
|
|
<div class="intro-screen" id="introScreen">
|
|
<div class="intro-title">🌋 火山修复流程</div>
|
|
<div class="intro-subtitle">语言表达模块诊断与修复原型</div>
|
|
<button class="action-btn intro-btn" onclick="startGame()">开始诊断</button>
|
|
<div style="margin-top: 40px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.2);">
|
|
<div style="color: #94a3b8; margin-bottom: 15px;">测试面板(跳过流程直接测试)</div>
|
|
<div style="display: flex; gap: 10px; flex-wrap: wrap; justify-content: center;">
|
|
<button class="action-btn" style="background: #475569; padding: 8px 16px; font-size: 0.9em;" onclick="testModule('voice')">发声单元</button>
|
|
<button class="action-btn" style="background: #475569; padding: 8px 16px; font-size: 0.9em;" onclick="testModule('trigger')">触发器</button>
|
|
<button class="action-btn" style="background: #6366f1; padding: 8px 16px; font-size: 0.9em;" onclick="testModule('processor')">处理器观察</button>
|
|
<button class="action-btn" style="background: #475569; padding: 8px 16px; font-size: 0.9em;" onclick="testModule('game1')">第一次清理</button>
|
|
<button class="action-btn" style="background: #475569; padding: 8px 16px; font-size: 0.9em;" onclick="testModule('game2')">第二次清理</button>
|
|
<button class="action-btn" style="background: #475569; padding: 8px 16px; font-size: 0.9em;" onclick="testModule('game3')">第三次清理</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<!-- 顶部状态栏 -->
|
|
<div class="status-bar">
|
|
<h1>🌋 火山 - 表达模块诊断</h1>
|
|
<div class="phase-indicator">
|
|
<div class="phase-dot" id="phase1"></div>
|
|
<div class="phase-dot" id="phase2"></div>
|
|
<div class="phase-dot" id="phase3"></div>
|
|
<div class="phase-dot" id="phase4"></div>
|
|
<div class="phase-dot" id="phase5"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 主要内容区 -->
|
|
<div class="main-content">
|
|
<!-- 左侧面板 - 模块选择 -->
|
|
<div class="module-panel">
|
|
<h2>表达模块结构</h2>
|
|
|
|
<button class="module-btn" id="btnTrigger" onclick="showModule('trigger')">
|
|
📊 触发器(情绪输入)
|
|
<div class="status" id="triggerStatus">待检查</div>
|
|
</button>
|
|
|
|
<button class="module-btn" id="btnVoice" onclick="showModule('voice')">
|
|
🔊 发声单元(输出)
|
|
<div class="status" id="voiceStatus">待检查</div>
|
|
</button>
|
|
|
|
<button class="module-btn locked" id="btnProcessor" onclick="showModule('processor')">
|
|
⚙️ 处理器(语言生成)
|
|
<div class="status locked" id="processorStatus">🔒 需要收集数据</div>
|
|
</button>
|
|
|
|
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.1);">
|
|
<h2>游戏阶段</h2>
|
|
<button class="module-btn" id="btnGame1" onclick="startParticleGame(1)" style="display:none;">
|
|
🎮 第一次清理
|
|
</button>
|
|
<button class="module-btn" id="btnGame2" onclick="startParticleGame(2)" style="display:none;">
|
|
🎮 第二次清理
|
|
</button>
|
|
<button class="module-btn" id="btnGame3" onclick="startParticleGame(3)" style="display:none;">
|
|
🎮 第三次清理
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 中央区域 -->
|
|
<div class="game-area" id="gameArea">
|
|
<!-- 欢迎界面 -->
|
|
<div class="diagnosis-view" id="welcomeView">
|
|
<div class="diagnosis-title">表达模块诊断系统</div>
|
|
<div class="diagnosis-content">
|
|
<div class="data-card">
|
|
<h3>患者信息</h3>
|
|
<div class="data-row">
|
|
<span class="data-label">编号</span>
|
|
<span class="data-value">火山 (HUOSHAN-001)</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">类型</span>
|
|
<span class="data-value">销售机器人</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">症状</span>
|
|
<span class="data-value warning">不停说笑话、结巴</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-card">
|
|
<h3>诊断流程</h3>
|
|
<p style="color: #94a3b8; line-height: 1.8;">
|
|
1. 检查<strong>发声单元</strong>,提取语音参数<br>
|
|
2. 检查<strong>触发器</strong>,分析情绪波形<br>
|
|
3. 进入<strong>处理器</strong>,定位并修复问题
|
|
</p>
|
|
</div>
|
|
|
|
<p style="color: #64ffda; margin-top: auto;">
|
|
请从左侧选择模块开始诊断
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 发声单元界面 -->
|
|
<div class="diagnosis-view hidden" id="voiceView">
|
|
<div class="diagnosis-title">🔊 发声单元 - 语音参数提取</div>
|
|
<div class="diagnosis-content">
|
|
<div class="data-card">
|
|
<h3>提取语音数据</h3>
|
|
<p style="color: #94a3b8; margin-bottom: 15px;">
|
|
需要提取语音参数以供处理器定位问题
|
|
</p>
|
|
<button class="action-btn" id="extractBtn" onclick="extractVoiceData()">
|
|
开始提取
|
|
</button>
|
|
</div>
|
|
|
|
<div class="data-card hidden" id="voiceResult">
|
|
<h3>提取结果</h3>
|
|
<div class="data-row">
|
|
<span class="data-label">内容解析</span>
|
|
<span class="data-value normal">✓ 正常</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">语法结构</span>
|
|
<span class="data-value normal">✓ 正常</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">语调参数</span>
|
|
<span class="data-value warning">⚠️ 检测到异常</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-card hidden" id="voiceDetail">
|
|
<h3>语调异常详情</h3>
|
|
<div class="data-row">
|
|
<span class="data-label">微颤</span>
|
|
<span class="data-value warning">检测到</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">气息不稳</span>
|
|
<span class="data-value warning">检测到</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">重音偏移</span>
|
|
<span class="data-value warning">检测到</span>
|
|
</div>
|
|
<p style="color: #fbbf24; margin-top: 10px; font-size: 0.9em;">
|
|
备注:这些特征与输出内容的情绪不匹配
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 触发器界面 -->
|
|
<div class="diagnosis-view hidden" id="triggerView">
|
|
<div class="diagnosis-title">📊 触发器 - 情绪波形分析</div>
|
|
<div class="diagnosis-content">
|
|
<div class="data-card">
|
|
<h3>采集情绪波形</h3>
|
|
<button class="action-btn" id="captureBtn" onclick="captureWaveform()">
|
|
开始采集
|
|
</button>
|
|
</div>
|
|
|
|
<div class="data-card hidden" id="waveformCard">
|
|
<h3>当前波形</h3>
|
|
<div class="waveform-container">
|
|
<canvas id="waveformCanvas" class="waveform"></canvas>
|
|
</div>
|
|
<p style="color: #94a3b8; font-size: 0.9em;">
|
|
请与情绪参考库对比,选择最匹配的情绪类型
|
|
</p>
|
|
</div>
|
|
|
|
<div class="hidden" id="emotionSelector">
|
|
<h3 style="color: #94a3b8; margin-bottom: 10px;">情绪参考库</h3>
|
|
<div class="emotion-selector">
|
|
<div class="emotion-option" onclick="selectEmotion(this, 'happy', 12)">
|
|
😊 快乐
|
|
<div class="match">12% 匹配</div>
|
|
</div>
|
|
<div class="emotion-option" onclick="selectEmotion(this, 'sad', 35)">
|
|
😢 悲伤
|
|
<div class="match">35% 匹配</div>
|
|
</div>
|
|
<div class="emotion-option" onclick="selectEmotion(this, 'fear', 87)">
|
|
😰 恐惧
|
|
<div class="match">87% 匹配</div>
|
|
</div>
|
|
<div class="emotion-option" onclick="selectEmotion(this, 'anger', 28)">
|
|
😠 愤怒
|
|
<div class="match">28% 匹配</div>
|
|
</div>
|
|
</div>
|
|
<button class="action-btn" id="confirmEmotionBtn" onclick="confirmEmotion()" disabled>
|
|
确认分析结果
|
|
</button>
|
|
</div>
|
|
|
|
<div class="data-card hidden" id="emotionResult">
|
|
<h3>分析结论</h3>
|
|
<div class="data-row">
|
|
<span class="data-label">主导情绪</span>
|
|
<span class="data-value error">恐惧 (87%)</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">次要情绪</span>
|
|
<span class="data-value warning">焦虑、不安</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">情绪强度</span>
|
|
<span class="data-value error">高强度负面</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 处理器解锁界面 -->
|
|
<div class="diagnosis-view hidden" id="processorUnlockView">
|
|
<div class="diagnosis-title">⚙️ 处理器 - 准备进入</div>
|
|
<div class="diagnosis-content">
|
|
<div class="data-card">
|
|
<h3>数据汇总</h3>
|
|
<div class="data-row">
|
|
<span class="data-label">触发器(输入)</span>
|
|
<span class="data-value error">恐惧/焦虑</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">发声单元(输出)</span>
|
|
<span class="data-value warning">笑话(语调异常)</span>
|
|
</div>
|
|
<div class="data-row">
|
|
<span class="data-label">问题定位</span>
|
|
<span class="data-value">输入输出不匹配</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-card" style="background: rgba(100,255,218,0.1); border-color: rgba(100,255,218,0.3);">
|
|
<p style="color: #64ffda; line-height: 1.8;">
|
|
<strong>诊断结论:</strong><br>
|
|
输入的情绪是恐惧...<br>
|
|
但输出的是笑话,语调还泄露了真实情绪...<br>
|
|
问题一定出在中间的处理过程。
|
|
</p>
|
|
</div>
|
|
|
|
<button class="action-btn" onclick="enterProcessor()">
|
|
进入处理器
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 粒子游戏界面 -->
|
|
<div class="particle-game hidden" id="particleGame">
|
|
<svg class="connections-svg" id="connectionsSvg"></svg>
|
|
<div id="particlesContainer"></div>
|
|
|
|
<!-- 语言生成显示器 -->
|
|
<div class="language-display hidden" id="languageDisplay">
|
|
<div class="display-label">语言生成中</div>
|
|
<div class="display-text" id="displayText"></div>
|
|
</div>
|
|
|
|
<div class="game-overlay">
|
|
<div class="game-instruction" id="gameInstruction">
|
|
左键排斥粒子 | 右键吸引粒子 | 目标:让绿色粒子相连
|
|
</div>
|
|
<div class="game-progress">
|
|
<span class="progress-label">整合度</span>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="integrationFill" style="width: 0%"></div>
|
|
</div>
|
|
<span id="integrationPercent">0%</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 火山气泡 -->
|
|
<div class="huoshan-bubble" id="huoshanBubble"></div>
|
|
|
|
<!-- 结果屏幕 -->
|
|
<div class="result-screen" id="resultScreen">
|
|
<div class="result-text" id="resultText">连接成功!</div>
|
|
<div class="result-detail" id="resultDetail">火山说出:"有点担心..."</div>
|
|
<button class="action-btn" id="resultBtn" onclick="handleResultAction()">继续</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 最终结果界面 -->
|
|
<div class="diagnosis-view hidden" id="finalView">
|
|
<div class="diagnosis-title" style="color: #4ade80;">✨ 修复完成</div>
|
|
<div class="diagnosis-content" style="align-items: center; justify-content: center; text-align: center;">
|
|
<div style="font-size: 1.3em; color: #64ffda; margin-bottom: 20px;">
|
|
✓ 真实表达通路已稳定
|
|
</div>
|
|
<div style="color: #e8e8e8; line-height: 2; font-size: 1.1em;">
|
|
火山:"我害怕被抛弃。"<br>
|
|
火山:"我害怕如果不有趣,就没人要我了。"<br>
|
|
<br>
|
|
<span style="color: #94a3b8;">(停顿)</span><br>
|
|
<br>
|
|
火山:"说出来之后...好像没那么可怕。"<br>
|
|
火山:"谢谢你愿意听我说真话。"
|
|
</div>
|
|
<button class="action-btn" style="margin-top: 30px;" onclick="location.reload()">
|
|
重新开始
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧面板 - 信号分析 -->
|
|
<div class="pathway-panel">
|
|
<h2>🔬 信号竞争分析</h2>
|
|
|
|
<!-- 未分析状态 -->
|
|
<div class="analysis-pending" id="analysisPending">
|
|
<div class="icon">📊</div>
|
|
<div>尚未进行信号分析</div>
|
|
<div style="font-size: 0.85em; margin-top: 8px; color: #475569;">
|
|
需要在处理器中观察到异常后触发
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 分析中动画 -->
|
|
<div class="analyzing hidden" id="analyzing">
|
|
<div class="spinner"></div>
|
|
<div style="color: #64ffda;">追踪信号来源...</div>
|
|
<div style="font-size: 0.85em; margin-top: 8px; color: #94a3b8;">
|
|
检测竞争路径中
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 拔河条(分析完成后显示) -->
|
|
<div class="tugofwar-container hidden" id="tugofwarContainer">
|
|
<div class="tugofwar-labels">
|
|
<span class="tugofwar-label-left">💚 真实表达</span>
|
|
<span class="tugofwar-label-right">😂 幽默防御</span>
|
|
</div>
|
|
<div class="tugofwar-bar">
|
|
<div class="tugofwar-indicator winning-right" id="tugofwarIndicator" style="left: 85%;">
|
|
⚡
|
|
</div>
|
|
</div>
|
|
<div class="tugofwar-marks">
|
|
<span>强</span>
|
|
<span>均衡</span>
|
|
<span>强</span>
|
|
</div>
|
|
<div class="tugofwar-status" id="tugofwarStatus">
|
|
<span style="color: #f87171;">幽默防御占据主导</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 分析日志 -->
|
|
<div class="hidden" id="analysisLog" style="margin-top: 15px; padding: 12px; background: rgba(0,0,0,0.2); border-radius: 8px; font-size: 0.8em; font-family: monospace;">
|
|
<div style="color: #64748b; margin-bottom: 8px;">// 分析日志</div>
|
|
<div id="logContent" style="color: #94a3b8; line-height: 1.6;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 对话框 -->
|
|
<div class="dialog-box" id="dialogBox">
|
|
<div class="dialog-speaker" id="dialogSpeaker">医生</div>
|
|
<div class="dialog-text" id="dialogText">让我检查一下你的表达模块。</div>
|
|
<div class="dialog-continue">点击继续 ▼</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 游戏状态
|
|
const state = {
|
|
currentPhase: 0,
|
|
voiceChecked: false,
|
|
triggerChecked: false,
|
|
processorUnlocked: false,
|
|
gameRound: 0,
|
|
blockageLevel: 100,
|
|
tugofwarPosition: 85, // 0=真实表达, 100=幽默防御
|
|
analysisComplete: false,
|
|
selectedEmotion: null,
|
|
particles: [],
|
|
isGameRunning: false,
|
|
mousePos: { x: 0, y: 0 },
|
|
isMouseDown: false,
|
|
mouseButton: 0
|
|
};
|
|
|
|
// 游戏配置
|
|
const gameConfigs = [
|
|
null, // 占位
|
|
{ targetCount: 4, nonTargetCount: 8, targetSentence: "有点担心", reboundTo: 95 },
|
|
{ targetCount: 5, nonTargetCount: 10, targetSentence: "我很害怕", reboundTo: 75 },
|
|
{ targetCount: 6, nonTargetCount: 8, targetSentence: "我害怕被抛弃", reboundTo: 0 }
|
|
];
|
|
|
|
// 对话队列
|
|
let dialogQueue = [];
|
|
let dialogCallback = null;
|
|
|
|
// 开始游戏
|
|
function startGame() {
|
|
document.getElementById('introScreen').classList.add('hidden');
|
|
updatePhase(1);
|
|
showDialog([
|
|
{ speaker: "系统", text: "患者火山已进入诊疗室。" },
|
|
{ speaker: "火山", text: "嘿!你知道销售机器人最怕什么吗?打折季!因为我们的价值也在——哈哈,开玩笑的!" },
|
|
{ speaker: "医生", text: "火山,你还好吗?你的表达好像有点...不太对劲。" },
|
|
{ speaker: "火山", text: "不对劲?哈哈,你知道'不对劲'和我的销售业绩有什么共同点吗?都——" },
|
|
{ speaker: "医生", text: "让我检查一下你的表达模块吧。" }
|
|
]);
|
|
}
|
|
|
|
// 测试面板 - 直接跳转到指定模块
|
|
function testModule(module) {
|
|
document.getElementById('introScreen').classList.add('hidden');
|
|
|
|
// 预设状态
|
|
state.voiceChecked = true;
|
|
state.triggerChecked = true;
|
|
state.processorUnlocked = true;
|
|
|
|
document.getElementById('voiceStatus').textContent = '✓ 已提取';
|
|
document.getElementById('triggerStatus').textContent = '✓ 已分析';
|
|
document.getElementById('btnProcessor').classList.remove('locked');
|
|
document.getElementById('processorStatus').textContent = '可以进入';
|
|
document.getElementById('processorStatus').classList.remove('locked');
|
|
|
|
if (module === 'voice') {
|
|
showModule('voice');
|
|
updatePhase(1);
|
|
} else if (module === 'trigger') {
|
|
showModule('trigger');
|
|
updatePhase(1);
|
|
} else if (module === 'processor') {
|
|
// 直接进入处理器观察阶段(使用粒子游戏界面)
|
|
enterProcessor();
|
|
updatePhase(2);
|
|
} else if (module === 'game1') {
|
|
showAnalysisResult(85);
|
|
document.getElementById('btnGame1').style.display = 'block';
|
|
startParticleGame(1);
|
|
} else if (module === 'game2') {
|
|
// 显示拔河条(第二次清理时已经分析过了)
|
|
showAnalysisResult(85);
|
|
document.getElementById('btnGame2').style.display = 'block';
|
|
startParticleGame(2);
|
|
} else if (module === 'game3') {
|
|
// 显示拔河条
|
|
showAnalysisResult(70);
|
|
document.getElementById('btnGame3').style.display = 'block';
|
|
startParticleGame(3);
|
|
}
|
|
}
|
|
|
|
// 直接显示分析结果(用于测试跳转)
|
|
function showAnalysisResult(position) {
|
|
state.analysisComplete = true;
|
|
document.getElementById('analysisPending').classList.add('hidden');
|
|
document.getElementById('analyzing').classList.add('hidden');
|
|
document.getElementById('tugofwarContainer').classList.remove('hidden');
|
|
document.getElementById('analysisLog').classList.remove('hidden');
|
|
updateTugofwar(position, false);
|
|
}
|
|
|
|
// 运行信号分析(第一次清理后触发)
|
|
function runSignalAnalysis(callback) {
|
|
document.getElementById('analysisPending').classList.add('hidden');
|
|
document.getElementById('analyzing').classList.remove('hidden');
|
|
|
|
setTimeout(() => {
|
|
document.getElementById('analyzing').classList.add('hidden');
|
|
document.getElementById('tugofwarContainer').classList.remove('hidden');
|
|
document.getElementById('analysisLog').classList.remove('hidden');
|
|
|
|
// 添加日志
|
|
addAnalysisLog('检测到两条竞争通路');
|
|
setTimeout(() => addAnalysisLog('通路A: 真实情绪表达'), 300);
|
|
setTimeout(() => addAnalysisLog('通路B: 幽默防御机制'), 600);
|
|
setTimeout(() => addAnalysisLog('当前状态: 通路B主导'), 900);
|
|
|
|
state.analysisComplete = true;
|
|
|
|
if (callback) setTimeout(callback, 1200);
|
|
}, 1500);
|
|
}
|
|
|
|
// 添加分析日志
|
|
function addAnalysisLog(text) {
|
|
const log = document.getElementById('logContent');
|
|
log.innerHTML += `> ${text}<br>`;
|
|
log.scrollTop = log.scrollHeight;
|
|
}
|
|
|
|
// 更新拔河条位置(0=真实表达胜, 100=幽默防御胜)
|
|
function updateTugofwar(position, animate = true) {
|
|
state.tugofwarPosition = position;
|
|
const indicator = document.getElementById('tugofwarIndicator');
|
|
const status = document.getElementById('tugofwarStatus');
|
|
|
|
indicator.style.left = position + '%';
|
|
|
|
// 更新指示器样式
|
|
indicator.classList.remove('winning-left', 'winning-right', 'balanced');
|
|
if (position < 40) {
|
|
indicator.classList.add('winning-left');
|
|
status.innerHTML = '<span style="color: #4ade80;">真实表达正在突破!</span>';
|
|
} else if (position > 60) {
|
|
indicator.classList.add('winning-right');
|
|
status.innerHTML = '<span style="color: #f87171;">幽默防御占据主导</span>';
|
|
} else {
|
|
indicator.classList.add('balanced');
|
|
status.innerHTML = '<span style="color: #fbbf24;">双方势均力敌</span>';
|
|
}
|
|
}
|
|
|
|
// 拔河回弹效果
|
|
function tugofwarRebound(target, rebound, callback) {
|
|
updateTugofwar(target);
|
|
addAnalysisLog(`信号位移: ${100 - target}%`);
|
|
|
|
if (rebound > target) {
|
|
setTimeout(() => {
|
|
updateTugofwar(rebound);
|
|
addAnalysisLog('检测到回弹力...');
|
|
addAnalysisLog(`回弹至: ${100 - rebound}%`);
|
|
if (callback) callback();
|
|
}, 1500);
|
|
} else {
|
|
addAnalysisLog('通路稳定!');
|
|
if (callback) setTimeout(callback, 500);
|
|
}
|
|
}
|
|
|
|
// 显示对话
|
|
function showDialog(dialogs, callback = null) {
|
|
dialogQueue = [...dialogs];
|
|
dialogCallback = callback;
|
|
showNextDialog();
|
|
}
|
|
|
|
function showNextDialog() {
|
|
if (dialogQueue.length === 0) {
|
|
document.getElementById('dialogBox').classList.remove('show');
|
|
if (dialogCallback) dialogCallback();
|
|
return;
|
|
}
|
|
|
|
const dialog = dialogQueue.shift();
|
|
document.getElementById('dialogSpeaker').textContent = dialog.speaker;
|
|
document.getElementById('dialogText').textContent = dialog.text;
|
|
document.getElementById('dialogBox').classList.add('show');
|
|
}
|
|
|
|
document.getElementById('dialogBox').addEventListener('click', showNextDialog);
|
|
|
|
// 更新阶段指示器
|
|
function updatePhase(phase) {
|
|
state.currentPhase = phase;
|
|
for (let i = 1; i <= 5; i++) {
|
|
const dot = document.getElementById('phase' + i);
|
|
dot.classList.remove('active', 'completed');
|
|
if (i < phase) dot.classList.add('completed');
|
|
if (i === phase) dot.classList.add('active');
|
|
}
|
|
}
|
|
|
|
// 显示模块
|
|
function showModule(module) {
|
|
// 隐藏所有视图
|
|
document.querySelectorAll('.diagnosis-view, .particle-game').forEach(el => {
|
|
el.classList.add('hidden');
|
|
});
|
|
|
|
// 更新按钮状态
|
|
document.querySelectorAll('.module-btn').forEach(btn => btn.classList.remove('active'));
|
|
|
|
if (module === 'voice') {
|
|
document.getElementById('voiceView').classList.remove('hidden');
|
|
document.getElementById('btnVoice').classList.add('active');
|
|
} else if (module === 'trigger') {
|
|
document.getElementById('triggerView').classList.remove('hidden');
|
|
document.getElementById('btnTrigger').classList.add('active');
|
|
} else if (module === 'processor') {
|
|
if (!state.processorUnlocked) {
|
|
if (state.voiceChecked && state.triggerChecked) {
|
|
unlockProcessor();
|
|
}
|
|
return;
|
|
}
|
|
document.getElementById('btnProcessor').classList.add('active');
|
|
// 显示游戏按钮
|
|
if (state.gameRound === 0) {
|
|
document.getElementById('btnGame1').style.display = 'block';
|
|
}
|
|
}
|
|
}
|
|
|
|
// 提取语音数据
|
|
function extractVoiceData() {
|
|
const btn = document.getElementById('extractBtn');
|
|
btn.disabled = true;
|
|
btn.textContent = '提取中...';
|
|
|
|
setTimeout(() => {
|
|
document.getElementById('voiceResult').classList.remove('hidden');
|
|
|
|
setTimeout(() => {
|
|
document.getElementById('voiceDetail').classList.remove('hidden');
|
|
state.voiceChecked = true;
|
|
document.getElementById('voiceStatus').textContent = '✓ 已提取(语调异常)';
|
|
document.getElementById('voiceStatus').classList.add('warning');
|
|
|
|
checkProcessorUnlock();
|
|
|
|
showDialog([
|
|
{ speaker: "医生", text: "奇怪...他说的是笑话,但语调好像不太对。" },
|
|
{ speaker: "医生", text: "先记下来,去看看情绪触发器。" }
|
|
]);
|
|
}, 500);
|
|
}, 1000);
|
|
}
|
|
|
|
// 采集波形
|
|
function captureWaveform() {
|
|
const btn = document.getElementById('captureBtn');
|
|
btn.disabled = true;
|
|
btn.textContent = '采集中...';
|
|
|
|
setTimeout(() => {
|
|
document.getElementById('waveformCard').classList.remove('hidden');
|
|
drawWaveform();
|
|
|
|
setTimeout(() => {
|
|
document.getElementById('emotionSelector').classList.remove('hidden');
|
|
btn.style.display = 'none';
|
|
}, 500);
|
|
}, 1000);
|
|
}
|
|
|
|
// 绘制波形
|
|
function drawWaveform() {
|
|
const canvas = document.getElementById('waveformCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
canvas.width = canvas.parentElement.clientWidth;
|
|
canvas.height = 60;
|
|
|
|
ctx.strokeStyle = '#ff6b6b';
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
|
|
for (let x = 0; x < canvas.width; x++) {
|
|
const y = 30 + Math.sin(x * 0.05) * 15 + Math.sin(x * 0.12) * 10 + Math.random() * 8;
|
|
if (x === 0) ctx.moveTo(x, y);
|
|
else ctx.lineTo(x, y);
|
|
}
|
|
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 选择情绪
|
|
function selectEmotion(element, emotion, match) {
|
|
document.querySelectorAll('.emotion-option').forEach(el => el.classList.remove('selected'));
|
|
element.classList.add('selected');
|
|
state.selectedEmotion = { emotion, match };
|
|
document.getElementById('confirmEmotionBtn').disabled = false;
|
|
}
|
|
|
|
// 确认情绪分析
|
|
function confirmEmotion() {
|
|
if (state.selectedEmotion.emotion === 'fear') {
|
|
document.getElementById('emotionResult').classList.remove('hidden');
|
|
state.triggerChecked = true;
|
|
document.getElementById('triggerStatus').textContent = '✓ 已分析(恐惧)';
|
|
document.getElementById('triggerStatus').classList.add('warning');
|
|
|
|
checkProcessorUnlock();
|
|
|
|
showDialog([
|
|
{ speaker: "医生", text: "原来如此...这明显是恐惧和焦虑的波形。" },
|
|
{ speaker: "医生", text: "但他说出来的是笑话...输入和输出完全对不上。" }
|
|
]);
|
|
} else {
|
|
showDialog([
|
|
{ speaker: "系统", text: "匹配度较低,请重新选择。" }
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 检查是否可以解锁处理器
|
|
function checkProcessorUnlock() {
|
|
if (state.voiceChecked && state.triggerChecked && !state.processorUnlocked) {
|
|
document.getElementById('btnProcessor').classList.remove('locked');
|
|
document.getElementById('processorStatus').textContent = '可以进入';
|
|
document.getElementById('processorStatus').classList.remove('locked');
|
|
}
|
|
}
|
|
|
|
// 解锁处理器
|
|
function unlockProcessor() {
|
|
state.processorUnlocked = true;
|
|
document.getElementById('processorUnlockView').classList.remove('hidden');
|
|
updatePhase(2);
|
|
}
|
|
|
|
// 进入处理器
|
|
// 入场动画状态
|
|
let entranceState = {
|
|
phase: 'idle', // idle, generating, disrupting, analyzing, playing
|
|
allowInput: false,
|
|
entranceLoop: null
|
|
};
|
|
|
|
function enterProcessor() {
|
|
document.getElementById('processorUnlockView').classList.add('hidden');
|
|
|
|
// 重置状态
|
|
entranceState = { phase: 'generating', allowInput: false, entranceLoop: null };
|
|
state.gameRound = 1;
|
|
state.isGameRunning = false;
|
|
|
|
// 显示粒子游戏界面
|
|
document.querySelectorAll('.diagnosis-view').forEach(el => el.classList.add('hidden'));
|
|
document.getElementById('particleGame').classList.remove('hidden');
|
|
|
|
// 显示游戏UI(但先禁用输入)
|
|
document.querySelector('.game-overlay').style.display = '';
|
|
document.getElementById('gameInstruction').textContent = '[ 处理器检查中... ]';
|
|
document.getElementById('integrationFill').style.width = '0%';
|
|
document.getElementById('integrationPercent').textContent = '0%';
|
|
document.getElementById('resultScreen').classList.remove('show');
|
|
|
|
// 显示语言生成显示器
|
|
document.getElementById('languageDisplay').classList.remove('hidden');
|
|
document.getElementById('displayText').textContent = '';
|
|
document.getElementById('displayText').className = 'display-text generating';
|
|
|
|
showDialog([
|
|
{ speaker: "医生", text: "现在我知道了——输入是恐惧,输出却变成了笑话。" },
|
|
{ speaker: "医生", text: "问题一定出在中间的处理过程。让我观察一下..." }
|
|
], () => {
|
|
playEntranceSequence();
|
|
});
|
|
}
|
|
|
|
// 入场动画序列
|
|
function playEntranceSequence() {
|
|
const container = document.getElementById('particlesContainer');
|
|
const gameArea = document.getElementById('particleGame');
|
|
const config = gameConfigs[1];
|
|
const rect = gameArea.getBoundingClientRect();
|
|
const width = rect.width;
|
|
const height = rect.height;
|
|
const centerX = width / 2;
|
|
const centerY = height / 2;
|
|
|
|
container.innerHTML = '';
|
|
document.getElementById('connectionsSvg').innerHTML = '';
|
|
state.particles = [];
|
|
|
|
const targetChars = ['我', '害', '怕', '被'];
|
|
const nonTargetChars = ['哈', '笑', '话', '嘿', '呵', '好', '棒', '赞'];
|
|
const displayText = document.getElementById('displayText');
|
|
let generatedText = '';
|
|
|
|
// 阶段1:绿色粒子一个一个在中心生成
|
|
document.getElementById('gameInstruction').textContent = '[ 语言生成中... ]';
|
|
|
|
let greenIndex = 0;
|
|
const generateGreen = () => {
|
|
if (greenIndex >= config.targetCount) {
|
|
// 绿色生成完毕,开始聚合循环
|
|
startAggregationLoop();
|
|
return;
|
|
}
|
|
|
|
const i = greenIndex;
|
|
const angle = (Math.PI * 2 / config.targetCount) * i + Math.random() * 0.3;
|
|
const radius = 30 + Math.random() * 20;
|
|
|
|
const particle = {
|
|
id: state.particles.length,
|
|
isTarget: true,
|
|
x: centerX + Math.cos(angle) * radius - 30,
|
|
y: centerY + Math.sin(angle) * radius - 30,
|
|
vx: (Math.random() - 0.5) * 0.5,
|
|
vy: (Math.random() - 0.5) * 0.5,
|
|
char: targetChars[i % targetChars.length]
|
|
};
|
|
|
|
const el = document.createElement('div');
|
|
el.className = 'particle target';
|
|
el.textContent = particle.char;
|
|
el.style.left = particle.x + 'px';
|
|
el.style.top = particle.y + 'px';
|
|
el.style.opacity = '0';
|
|
el.style.transform = 'scale(0.3)';
|
|
el.id = 'particle-' + particle.id;
|
|
container.appendChild(el);
|
|
state.particles.push(particle);
|
|
|
|
// 淡入动画
|
|
setTimeout(() => {
|
|
el.style.transition = 'opacity 0.3s, transform 0.3s';
|
|
el.style.opacity = '1';
|
|
el.style.transform = 'scale(1)';
|
|
}, 50);
|
|
|
|
// 更新显示器文字
|
|
generatedText += particle.char;
|
|
displayText.textContent = generatedText + '_';
|
|
|
|
greenIndex++;
|
|
setTimeout(generateGreen, 400);
|
|
};
|
|
|
|
// 开始生成绿色粒子
|
|
setTimeout(generateGreen, 500);
|
|
|
|
// 聚合循环:绿色粒子互相吸引
|
|
function startAggregationLoop() {
|
|
displayText.textContent = generatedText + '...';
|
|
|
|
entranceState.entranceLoop = setInterval(() => {
|
|
// 绿色粒子向中心聚合
|
|
state.particles.filter(p => p.isTarget).forEach(particle => {
|
|
const dx = centerX - 30 - particle.x;
|
|
const dy = centerY - 30 - particle.y;
|
|
const dist = Math.hypot(dx, dy);
|
|
if (dist > 10) {
|
|
particle.vx += (dx / dist) * 0.15;
|
|
particle.vy += (dy / dist) * 0.15;
|
|
}
|
|
|
|
// 更新位置
|
|
particle.x += particle.vx;
|
|
particle.y += particle.vy;
|
|
particle.vx *= 0.95;
|
|
particle.vy *= 0.95;
|
|
|
|
const el = document.getElementById('particle-' + particle.id);
|
|
if (el) {
|
|
el.style.left = particle.x + 'px';
|
|
el.style.top = particle.y + 'px';
|
|
}
|
|
});
|
|
|
|
drawConnections();
|
|
}, 16);
|
|
|
|
// 1.5秒后,红色粒子涌入
|
|
setTimeout(startDisruption, 1500);
|
|
}
|
|
|
|
// 阶段2:红色粒子涌入打散
|
|
function startDisruption() {
|
|
entranceState.phase = 'disrupting';
|
|
document.getElementById('gameInstruction').innerHTML = '[ ⚠️ 检测到干扰信号!]';
|
|
displayText.className = 'display-text error';
|
|
|
|
// 创建红色粒子(从四周进入)
|
|
for (let i = 0; i < config.nonTargetCount; i++) {
|
|
const angle = (Math.PI * 2 / config.nonTargetCount) * i;
|
|
const startDist = Math.max(width, height) * 0.6;
|
|
|
|
const particle = {
|
|
id: state.particles.length,
|
|
isTarget: false,
|
|
x: centerX + Math.cos(angle) * startDist - 30,
|
|
y: centerY + Math.sin(angle) * startDist - 30,
|
|
vx: -Math.cos(angle) * 3, // 向中心冲
|
|
vy: -Math.sin(angle) * 3,
|
|
char: nonTargetChars[i % nonTargetChars.length]
|
|
};
|
|
|
|
const el = document.createElement('div');
|
|
el.className = 'particle non-target';
|
|
el.textContent = particle.char;
|
|
el.style.left = particle.x + 'px';
|
|
el.style.top = particle.y + 'px';
|
|
el.style.opacity = '0';
|
|
el.id = 'particle-' + particle.id;
|
|
container.appendChild(el);
|
|
state.particles.push(particle);
|
|
|
|
setTimeout(() => {
|
|
el.style.transition = 'opacity 0.2s';
|
|
el.style.opacity = '1';
|
|
}, i * 50);
|
|
}
|
|
|
|
// 更新显示器
|
|
setTimeout(() => {
|
|
displayText.textContent = '哈哈笑话...';
|
|
}, 800);
|
|
|
|
// 切换到干扰循环
|
|
clearInterval(entranceState.entranceLoop);
|
|
entranceState.entranceLoop = setInterval(() => {
|
|
const gameArea = document.getElementById('particleGame');
|
|
const rect = gameArea.getBoundingClientRect();
|
|
|
|
state.particles.forEach(particle => {
|
|
// 红色粒子向中心冲
|
|
if (!particle.isTarget) {
|
|
const dx = centerX - 30 - particle.x;
|
|
const dy = centerY - 30 - particle.y;
|
|
const dist = Math.hypot(dx, dy);
|
|
if (dist > 80) {
|
|
particle.vx += (dx / dist) * 0.2;
|
|
particle.vy += (dy / dist) * 0.2;
|
|
}
|
|
}
|
|
|
|
// 粒子之间的排斥力(红色排斥绿色)
|
|
state.particles.forEach(other => {
|
|
if (other.id === particle.id) return;
|
|
const dx = particle.x - other.x;
|
|
const dy = particle.y - other.y;
|
|
const dist = Math.hypot(dx, dy);
|
|
|
|
if (dist < 80 && dist > 1) {
|
|
const force = (80 - dist) / 80;
|
|
// 红色对绿色的排斥力更强
|
|
const multiplier = (!particle.isTarget && other.isTarget) ? 2.5 : 0.8;
|
|
particle.vx += (dx / dist) * force * multiplier;
|
|
particle.vy += (dy / dist) * force * multiplier;
|
|
}
|
|
});
|
|
|
|
// 更新位置
|
|
particle.x += particle.vx;
|
|
particle.y += particle.vy;
|
|
particle.vx *= 0.96;
|
|
particle.vy *= 0.96;
|
|
|
|
// 边界
|
|
if (particle.x < 30) { particle.x = 30; particle.vx *= -0.5; }
|
|
if (particle.x > rect.width - 90) { particle.x = rect.width - 90; particle.vx *= -0.5; }
|
|
if (particle.y < 60) { particle.y = 60; particle.vy *= -0.5; }
|
|
if (particle.y > rect.height - 90) { particle.y = rect.height - 90; particle.vy *= -0.5; }
|
|
|
|
const el = document.getElementById('particle-' + particle.id);
|
|
if (el) {
|
|
el.style.left = particle.x + 'px';
|
|
el.style.top = particle.y + 'px';
|
|
}
|
|
});
|
|
|
|
drawConnections();
|
|
}, 16);
|
|
|
|
// 2秒后进入分析阶段
|
|
setTimeout(finishEntrance, 2000);
|
|
}
|
|
|
|
// 入场完成
|
|
function finishEntrance() {
|
|
clearInterval(entranceState.entranceLoop);
|
|
entranceState.entranceLoop = null;
|
|
|
|
document.getElementById('gameInstruction').textContent = '[ 干扰递质劫持了语言生成 ]';
|
|
|
|
showDialog([
|
|
{ speaker: "医生", text: "看到了...干扰递质打乱了正常的语言生成。" },
|
|
{ speaker: "医生", text: "让我分析一下这个竞争模式..." }
|
|
], () => {
|
|
entranceState.phase = 'analyzing';
|
|
|
|
// 隐藏显示器
|
|
document.getElementById('languageDisplay').classList.add('hidden');
|
|
|
|
runSignalAnalysis(() => {
|
|
showDialog([
|
|
{ speaker: "医生", text: "找到原因了。真实表达被幽默防御压制。" },
|
|
{ speaker: "医生", text: "我需要帮火山清理这些干扰,让真实的声音能够连接。" }
|
|
], () => {
|
|
// 解锁输入,开始游戏
|
|
entranceState.phase = 'playing';
|
|
entranceState.allowInput = true;
|
|
state.isGameRunning = true;
|
|
|
|
document.getElementById('gameInstruction').textContent = '左键排斥粒子 | 右键吸引粒子 | 目标:让绿色粒子相连';
|
|
document.getElementById('btnGame1').style.display = 'block';
|
|
|
|
// 开始正常游戏循环
|
|
requestAnimationFrame(gameLoop);
|
|
showHuoshanBubble(1);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// 开始粒子游戏
|
|
function startParticleGame(round) {
|
|
state.gameRound = round;
|
|
state.isGameRunning = true;
|
|
entranceState.allowInput = true;
|
|
entranceState.phase = 'playing';
|
|
const config = gameConfigs[round];
|
|
|
|
// 隐藏其他视图
|
|
document.querySelectorAll('.diagnosis-view').forEach(el => el.classList.add('hidden'));
|
|
document.getElementById('particleGame').classList.remove('hidden');
|
|
|
|
// 确保正常游戏UI显示
|
|
document.querySelector('.game-overlay').style.display = '';
|
|
|
|
// 更新UI
|
|
document.getElementById('gameInstruction').textContent = '左键排斥粒子 | 右键吸引粒子 | 目标:让绿色粒子相连';
|
|
updatePhase(3);
|
|
|
|
// 隐藏结果屏幕
|
|
document.getElementById('resultScreen').classList.remove('show');
|
|
|
|
// 初始化粒子(第2、3轮需要重新初始化)
|
|
initParticles(config);
|
|
|
|
// 开始游戏循环
|
|
requestAnimationFrame(gameLoop);
|
|
|
|
// 显示火山气泡
|
|
showHuoshanBubble(round);
|
|
}
|
|
|
|
// 显示火山气泡
|
|
function showHuoshanBubble(round) {
|
|
const bubble = document.getElementById('huoshanBubble');
|
|
const texts = {
|
|
1: "哈哈,你在做什么?那些东西很重要的!",
|
|
2: "等等...你确定要这样吗...",
|
|
3: "我...我来试试..."
|
|
};
|
|
bubble.textContent = texts[round];
|
|
bubble.classList.add('show');
|
|
|
|
setTimeout(() => {
|
|
bubble.classList.remove('show');
|
|
}, 3000);
|
|
}
|
|
|
|
// 初始化粒子
|
|
function initParticles(config) {
|
|
const container = document.getElementById('particlesContainer');
|
|
const gameArea = document.getElementById('particleGame');
|
|
container.innerHTML = '';
|
|
state.particles = [];
|
|
|
|
const width = gameArea.clientWidth;
|
|
const height = gameArea.clientHeight;
|
|
|
|
// 创建目标粒子(绿色 - 真实表达)
|
|
for (let i = 0; i < config.targetCount; i++) {
|
|
createParticle(true, width, height, container);
|
|
}
|
|
|
|
// 创建非目标粒子(红色 - 干扰/笑话)
|
|
for (let i = 0; i < config.nonTargetCount; i++) {
|
|
createParticle(false, width, height, container);
|
|
}
|
|
}
|
|
|
|
// 创建粒子
|
|
function createParticle(isTarget, maxWidth, maxHeight, container) {
|
|
const particle = {
|
|
id: state.particles.length,
|
|
isTarget: isTarget,
|
|
x: Math.random() * (maxWidth - 100) + 50,
|
|
y: Math.random() * (maxHeight - 150) + 75,
|
|
vx: (Math.random() - 0.5) * 2,
|
|
vy: (Math.random() - 0.5) * 2,
|
|
char: isTarget ? ['真', '实', '表', '达', '我', '怕'][state.particles.filter(p => p.isTarget).length % 6] :
|
|
['哈', '笑', '话', '嘿', '呵'][Math.floor(Math.random() * 5)]
|
|
};
|
|
|
|
const el = document.createElement('div');
|
|
el.className = `particle ${isTarget ? 'target' : 'non-target'}`;
|
|
el.textContent = particle.char;
|
|
el.style.left = particle.x + 'px';
|
|
el.style.top = particle.y + 'px';
|
|
el.id = 'particle-' + particle.id;
|
|
|
|
container.appendChild(el);
|
|
state.particles.push(particle);
|
|
}
|
|
|
|
// 游戏循环
|
|
function gameLoop() {
|
|
if (!state.isGameRunning) return;
|
|
|
|
const gameArea = document.getElementById('particleGame');
|
|
const width = gameArea.clientWidth;
|
|
const height = gameArea.clientHeight;
|
|
|
|
// 更新粒子
|
|
updateNormalMode(width, height);
|
|
|
|
// 绘制连接线
|
|
drawConnections();
|
|
|
|
// 计算整合度
|
|
const integration = calculateIntegration();
|
|
document.getElementById('integrationFill').style.width = integration + '%';
|
|
document.getElementById('integrationPercent').textContent = Math.round(integration) + '%';
|
|
|
|
if (integration >= 100) {
|
|
completeGame();
|
|
}
|
|
|
|
requestAnimationFrame(gameLoop);
|
|
}
|
|
|
|
// 普通模式更新
|
|
function updateNormalMode(width, height) {
|
|
state.particles.forEach(particle => {
|
|
particle.x += particle.vx;
|
|
particle.y += particle.vy;
|
|
|
|
if (particle.x < 30 || particle.x > width - 90) particle.vx *= -1;
|
|
if (particle.y < 60 || particle.y > height - 90) particle.vy *= -1;
|
|
|
|
// 鼠标交互(只有在允许输入时才响应)
|
|
if (state.isMouseDown && entranceState.allowInput) {
|
|
const dx = particle.x - state.mousePos.x;
|
|
const dy = particle.y - state.mousePos.y;
|
|
const dist = Math.hypot(dx, dy);
|
|
|
|
if (dist < 120 && dist > 0) {
|
|
const force = (120 - dist) / 120;
|
|
if (state.mouseButton === 0) {
|
|
particle.vx += (dx / dist) * force * 3;
|
|
particle.vy += (dy / dist) * force * 3;
|
|
} else if (state.mouseButton === 2) {
|
|
particle.vx -= (dx / dist) * force * 1.5;
|
|
particle.vy -= (dy / dist) * force * 1.5;
|
|
}
|
|
}
|
|
}
|
|
|
|
particle.vx *= 0.98;
|
|
particle.vy *= 0.98;
|
|
|
|
const el = document.getElementById('particle-' + particle.id);
|
|
if (el) {
|
|
el.style.left = particle.x + 'px';
|
|
el.style.top = particle.y + 'px';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 绘制连接线
|
|
function drawConnections() {
|
|
const svg = document.getElementById('connectionsSvg');
|
|
svg.innerHTML = '';
|
|
|
|
const targets = state.particles.filter(p => p.isTarget);
|
|
|
|
for (let i = 0; i < targets.length; i++) {
|
|
for (let j = i + 1; j < targets.length; j++) {
|
|
const dist = Math.hypot(targets[i].x - targets[j].x, targets[i].y - targets[j].y);
|
|
if (dist < 120) {
|
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
line.setAttribute('x1', targets[i].x + 30);
|
|
line.setAttribute('y1', targets[i].y + 30);
|
|
line.setAttribute('x2', targets[j].x + 30);
|
|
line.setAttribute('y2', targets[j].y + 30);
|
|
line.setAttribute('class', 'connection-line');
|
|
line.style.stroke = 'rgba(100,255,218,0.6)';
|
|
line.style.opacity = 1 - dist / 120;
|
|
svg.appendChild(line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 计算整合度
|
|
function calculateIntegration() {
|
|
const targets = state.particles.filter(p => p.isTarget);
|
|
const nonTargets = state.particles.filter(p => !p.isTarget);
|
|
if (targets.length < 2) return 100;
|
|
|
|
const connectionDist = 120; // 连接距离
|
|
|
|
// 检查目标粒子是否都相连(使用BFS检查连通性)
|
|
const visited = new Set();
|
|
const queue = [targets[0]];
|
|
visited.add(targets[0].id);
|
|
|
|
while (queue.length > 0) {
|
|
const current = queue.shift();
|
|
for (const other of targets) {
|
|
if (!visited.has(other.id)) {
|
|
const dist = Math.hypot(current.x - other.x, current.y - other.y);
|
|
if (dist < connectionDist) {
|
|
visited.add(other.id);
|
|
queue.push(other);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查是否所有目标粒子都连通
|
|
const allConnected = visited.size === targets.length;
|
|
|
|
// 检查是否与非目标粒子分离
|
|
let separated = true;
|
|
for (const target of targets) {
|
|
for (const nonTarget of nonTargets) {
|
|
const dist = Math.hypot(target.x - nonTarget.x, target.y - nonTarget.y);
|
|
if (dist < connectionDist) {
|
|
separated = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!separated) break;
|
|
}
|
|
|
|
// 计算整合度
|
|
// 基础分:连通的目标粒子比例
|
|
const connectivityScore = (visited.size / targets.length) * 70;
|
|
|
|
// 额外分:如果与干扰分离
|
|
const separationScore = separated ? 30 : 0;
|
|
|
|
const integration = connectivityScore + separationScore;
|
|
|
|
// 如果完全连通且分离,返回100
|
|
if (allConnected && separated) {
|
|
return 100;
|
|
}
|
|
|
|
return Math.min(99, integration);
|
|
}
|
|
|
|
|
|
// 完成游戏
|
|
function completeGame() {
|
|
state.isGameRunning = false;
|
|
const config = gameConfigs[state.gameRound];
|
|
|
|
document.getElementById('resultText').textContent = '连接成功!';
|
|
document.getElementById('resultDetail').textContent = `火山说出:"${config.targetSentence}..."`;
|
|
|
|
if (state.gameRound < 3) {
|
|
document.getElementById('resultBtn').textContent = '继续';
|
|
} else {
|
|
document.getElementById('resultBtn').textContent = '查看结果';
|
|
}
|
|
|
|
setTimeout(() => {
|
|
document.getElementById('resultScreen').classList.add('show');
|
|
}, 500);
|
|
}
|
|
|
|
// 处理结果按钮
|
|
function handleResultAction() {
|
|
document.getElementById('resultScreen').classList.remove('show');
|
|
|
|
if (state.gameRound === 1) {
|
|
// 第一次清理完成后,触发信号分析
|
|
document.getElementById('btnGame1').style.display = 'none';
|
|
document.getElementById('particleGame').classList.add('hidden');
|
|
document.getElementById('welcomeView').classList.remove('hidden');
|
|
|
|
showDialog([
|
|
{ speaker: "医生", text: "连上了...但是等等,怎么断了?" },
|
|
{ speaker: "医生", text: "有什么东西在把它拉回去...让我追踪一下信号来源。" }
|
|
], () => {
|
|
// 触发信号分析
|
|
runSignalAnalysis(() => {
|
|
showDialog([
|
|
{ speaker: "医生", text: "找到了!原来有两条竞争的通路。" },
|
|
{ speaker: "医生", text: "真实表达和幽默防御在争夺主导权。" },
|
|
{ speaker: "医生", text: "现在幽默防御占上风...但我们可以改变这个。" }
|
|
], () => {
|
|
document.getElementById('btnGame2').style.display = 'block';
|
|
showDialog([{ speaker: "医生", text: "点击左侧的「第二次清理」继续。" }]);
|
|
});
|
|
});
|
|
});
|
|
} else if (state.gameRound === 2) {
|
|
// 第二次清理完成
|
|
document.getElementById('btnGame2').style.display = 'none';
|
|
document.getElementById('particleGame').classList.add('hidden');
|
|
document.getElementById('welcomeView').classList.remove('hidden');
|
|
|
|
// 拔河条移动后回弹
|
|
tugofwarRebound(40, 70, () => {
|
|
showDialog([
|
|
{ speaker: "医生", text: "有进展了!但幽默防御还在反扑..." },
|
|
{ speaker: "医生", text: "火山,再来一次。这次试着说出更多。" },
|
|
{ speaker: "火山", text: "我...我试试..." }
|
|
], () => {
|
|
document.getElementById('btnGame3').style.display = 'block';
|
|
showDialog([{ speaker: "医生", text: "点击左侧的「第三次清理」继续。" }]);
|
|
});
|
|
});
|
|
} else {
|
|
// 第三次清理完成 - 胜利!
|
|
document.getElementById('particleGame').classList.add('hidden');
|
|
|
|
// 拔河条完全倒向真实表达
|
|
updateTugofwar(5);
|
|
addAnalysisLog('真实表达通路已建立!');
|
|
addAnalysisLog('防御机制解除');
|
|
|
|
const statusEl = document.getElementById('tugofwarStatus');
|
|
statusEl.innerHTML = '<span style="color: #4ade80;">✓ 真实表达通路稳定</span>';
|
|
|
|
setTimeout(() => {
|
|
document.getElementById('finalView').classList.remove('hidden');
|
|
updatePhase(5);
|
|
|
|
showDialog([
|
|
{ speaker: "医生", text: "你做到了。这次是你自己说出来的。" },
|
|
{ speaker: "火山", text: "...是我们一起。" }
|
|
]);
|
|
}, 1000);
|
|
}
|
|
}
|
|
|
|
// 鼠标事件
|
|
const gameArea = document.getElementById('particleGame');
|
|
|
|
gameArea.addEventListener('mousemove', (e) => {
|
|
const rect = gameArea.getBoundingClientRect();
|
|
state.mousePos.x = e.clientX - rect.left - 30;
|
|
state.mousePos.y = e.clientY - rect.top - 30;
|
|
});
|
|
|
|
gameArea.addEventListener('mousedown', (e) => {
|
|
state.isMouseDown = true;
|
|
state.mouseButton = e.button;
|
|
e.preventDefault();
|
|
});
|
|
|
|
gameArea.addEventListener('mouseup', () => {
|
|
state.isMouseDown = false;
|
|
});
|
|
|
|
gameArea.addEventListener('mouseleave', () => {
|
|
state.isMouseDown = false;
|
|
});
|
|
|
|
gameArea.addEventListener('contextmenu', (e) => {
|
|
e.preventDefault();
|
|
});
|
|
|
|
// 窗口大小变化时重绘波形
|
|
window.addEventListener('resize', () => {
|
|
if (!document.getElementById('waveformCard').classList.contains('hidden')) {
|
|
drawWaveform();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|