Files
aibis-dream/prototype/huoshan_hardware.html
T
2026-01-22 16:27:05 +08:00

1794 lines
62 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=VT323&family=Orbitron:wght@400;700&family=Noto+Sans+SC:wght@300;400;500&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-dark: #1a1a2e;
--bg-panel: #16213e;
--border-metal: #4a5568;
--border-highlight: #718096;
--text-dim: #a0aec0;
--text-normal: #e2e8f0;
--accent-cyan: #64ffda;
--accent-green: #48bb78;
--accent-orange: #ed8936;
--accent-red: #fc8181;
--accent-blue: #63b3ed;
--accent-yellow: #f6e05e;
--metal-dark: #2d3748;
--metal-light: #4a5568;
}
body {
background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%);
color: var(--text-normal);
font-family: 'VT323', 'Noto Sans SC', monospace;
min-height: 100vh;
overflow: hidden;
}
/* ============ 主布局 ============ */
.game-container {
display: grid;
grid-template-columns: 1fr 320px;
height: 100vh;
}
/* ============ 主视图区域 ============ */
.main-view {
position: relative;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, #1a2a3a 0%, #0a0f1a 100%);
overflow: hidden;
}
/* 扫描线效果 */
.scanlines {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.05) 0px,
rgba(0, 0, 0, 0.05) 1px,
transparent 1px,
transparent 2px
);
z-index: 100;
}
/* ============ 机器人后脑视图 ============ */
.brain-view {
position: relative;
width: 700px;
height: 650px;
}
/* 后脑轮廓 */
.brain-shell {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 600px;
height: 600px;
background:
radial-gradient(ellipse 80% 70% at 50% 45%, #2a3a4a 0%, transparent 70%),
linear-gradient(180deg, #1a2535 0%, #0f1825 100%);
border-radius: 50% 50% 45% 45%;
border: 4px solid var(--metal-light);
box-shadow:
inset 0 -30px 60px rgba(0,0,0,0.5),
0 10px 40px rgba(0,0,0,0.5);
}
/* 内部框架 */
.brain-frame {
position: absolute;
top: 8%;
left: 50%;
transform: translateX(-50%);
width: 85%;
height: 80%;
border: 3px solid var(--metal-dark);
border-radius: 40% 40% 35% 35%;
background: rgba(10, 20, 30, 0.6);
}
/* 模块基础样式 */
.brain-module {
position: absolute;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.brain-module:hover {
transform: scale(1.05);
z-index: 10;
}
.module-label {
position: absolute;
padding: 6px 15px;
background: rgba(255,255,255,0.95);
border: 2px solid var(--accent-blue);
border-radius: 6px;
font-size: 1em;
color: #1a1a2e;
white-space: nowrap;
font-weight: bold;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
}
.module-connector {
position: absolute;
background: rgba(255,255,255,0.5);
z-index: 1;
}
/* 情绪模块 */
.module-emotion {
top: 25%;
left: 12%;
width: 100px;
height: 80px;
background:
radial-gradient(circle at 30% 30%, #3a4a5a, #2a3545);
border: 3px solid #5a6a7a;
}
.module-emotion .module-label {
top: 50%;
left: -80px;
transform: translateY(-50%);
}
.module-emotion .module-connector {
width: 40px;
height: 2px;
top: 50%;
left: -40px;
}
/* 逻辑模块 */
.module-logic {
top: 25%;
right: 12%;
width: 100px;
height: 80px;
background:
radial-gradient(circle at 30% 30%, #3a4a5a, #2a3545);
border: 3px solid #5a6a7a;
}
.module-logic .module-label {
top: 50%;
right: -80px;
transform: translateY(-50%);
}
.module-logic .module-connector {
width: 40px;
height: 2px;
top: 50%;
right: -40px;
}
/* 表达模块(主模块) */
.module-expression {
bottom: 18%;
left: 50%;
transform: translateX(-50%);
width: 140px;
height: 100px;
background:
linear-gradient(145deg, #3a4a5a, #2a3545);
border: 3px solid var(--accent-cyan);
box-shadow: 0 0 20px rgba(100, 255, 218, 0.3);
}
.module-expression .module-label {
bottom: -50px;
left: 50%;
transform: translateX(-50%);
border-color: var(--accent-cyan);
}
.module-expression .module-connector {
width: 2px;
height: 30px;
bottom: -30px;
left: 50%;
transform: translateX(-50%);
}
/* 中央处理器标记 */
.central-processor {
position: absolute;
top: 35%;
left: 50%;
transform: translateX(-50%);
width: 120px;
height: 120px;
background:
radial-gradient(circle, #4a5a6a 0%, #2a3545 70%);
border: 4px solid #6a7a8a;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.processor-inner {
width: 60%;
height: 60%;
background: linear-gradient(135deg, #5a6a7a, #3a4a5a);
border-radius: 50%;
border: 2px solid #7a8a9a;
}
/* 连接线 */
.connection-line {
position: absolute;
background: rgba(100, 255, 218, 0.3);
z-index: 0;
}
.line-emotion-center {
top: 33%;
left: 22%;
width: 80px;
height: 2px;
transform: rotate(15deg);
}
.line-logic-center {
top: 33%;
right: 22%;
width: 80px;
height: 2px;
transform: rotate(-15deg);
}
.line-center-expression {
top: 52%;
left: 50%;
width: 2px;
height: 80px;
transform: translateX(-50%);
}
/* 模块状态指示灯 */
.module-led {
width: 12px;
height: 12px;
border-radius: 50%;
background: #333;
margin-top: 8px;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.5);
}
.module-led.active {
background: var(--accent-green);
box-shadow: 0 0 10px var(--accent-green);
}
.module-led.warning {
background: var(--accent-orange);
box-shadow: 0 0 10px var(--accent-orange);
animation: led-blink 1s ease-in-out infinite;
}
.module-led.error {
background: var(--accent-red);
box-shadow: 0 0 10px var(--accent-red);
animation: led-blink 0.5s ease-in-out infinite;
}
@keyframes led-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* 信息标签 */
.info-tag {
position: absolute;
padding: 3px 8px;
background: rgba(0,0,0,0.7);
border: 1px solid var(--accent-yellow);
border-radius: 3px;
font-size: 0.75em;
color: var(--accent-yellow);
text-transform: uppercase;
}
.tag-memory {
top: 8%;
left: 50%;
transform: translateX(-50%);
}
.tag-extension {
top: 40%;
left: 50%;
transform: translateX(-50%);
}
.tag-vision {
top: 40%;
right: 18%;
}
/* ============ 表达模块详细视图 ============ */
.expression-detail-view {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, #0f1a25 0%, #1a2535 100%);
display: none;
padding: 30px;
}
.expression-detail-view.active {
display: flex;
flex-direction: column;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.detail-title {
font-family: 'Orbitron', sans-serif;
font-size: 1.3em;
color: var(--accent-cyan);
}
.back-btn {
padding: 8px 20px;
background: transparent;
border: 2px solid var(--text-dim);
border-radius: 5px;
color: var(--text-dim);
font-family: inherit;
font-size: 1em;
cursor: pointer;
transition: all 0.2s;
}
.back-btn:hover {
border-color: var(--accent-cyan);
color: var(--accent-cyan);
}
/* 表达模块硬件布局 */
.hardware-layout {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
padding: 20px;
background: rgba(255,255,255,0.02);
border: 2px solid var(--metal-dark);
border-radius: 12px;
}
/* 识别器 */
.recognizer-unit {
width: 180px;
padding: 15px 25px;
background: linear-gradient(145deg, #3a4a5a, #2a3545);
border: 3px solid var(--accent-blue);
border-radius: 10px;
text-align: center;
position: relative;
}
.recognizer-unit::after {
content: '';
position: absolute;
bottom: -20px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 15px solid var(--accent-blue);
}
.unit-name {
font-size: 1.1em;
color: var(--text-normal);
}
.unit-status {
font-size: 0.8em;
color: var(--text-dim);
margin-top: 5px;
}
/* 信号流向箭头 */
.flow-arrow {
width: 2px;
height: 25px;
background: linear-gradient(180deg, var(--accent-blue) 0%, var(--accent-cyan) 100%);
position: relative;
}
.flow-arrow::after {
content: '▼';
position: absolute;
bottom: -12px;
left: 50%;
transform: translateX(-50%);
color: var(--accent-cyan);
font-size: 0.8em;
}
/* 处理单元容器 */
.processing-container {
width: 100%;
max-width: 550px;
padding: 20px;
background: rgba(100, 180, 255, 0.05);
border: 2px solid var(--accent-blue);
border-radius: 12px;
position: relative;
}
.processing-title {
position: absolute;
top: -12px;
left: 20px;
background: #1a2535;
padding: 2px 10px;
font-size: 0.85em;
color: var(--accent-blue);
}
.processing-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.processing-unit {
aspect-ratio: 1;
background: linear-gradient(145deg, #2a3a4a, #1a2a3a);
border: 2px solid var(--metal-light);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.processing-unit:hover {
border-color: var(--accent-cyan);
transform: translateY(-2px);
}
.processing-unit.active {
border-color: var(--accent-green);
background: linear-gradient(145deg, #2a4a3a, #1a3a2a);
box-shadow: 0 0 15px rgba(72, 187, 120, 0.3);
}
.processing-unit.anomaly {
border-color: var(--accent-orange);
animation: unit-pulse 1.5s ease-in-out infinite;
}
@keyframes unit-pulse {
0%, 100% { box-shadow: 0 0 5px rgba(237, 137, 54, 0.3); }
50% { box-shadow: 0 0 20px rgba(237, 137, 54, 0.6); }
}
.unit-icon {
font-size: 1.5em;
margin-bottom: 4px;
}
.unit-label {
font-size: 0.75em;
color: var(--text-dim);
}
.processing-unit.active .unit-label {
color: var(--accent-green);
}
/* 生成器 */
.generator-unit {
width: 160px;
padding: 12px 20px;
background: linear-gradient(145deg, #3a4a5a, #2a3545);
border: 3px solid var(--accent-cyan);
border-radius: 10px;
text-align: center;
position: relative;
}
.generator-unit::before {
content: '';
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 15px solid var(--accent-cyan);
}
.generator-unit::after {
content: '';
position: absolute;
bottom: -20px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 15px solid var(--accent-cyan);
}
/* 发声单元容器 */
.voice-units-container {
display: flex;
gap: 15px;
padding: 20px 30px;
background: rgba(255,255,255,0.02);
border: 2px dashed var(--metal-light);
border-radius: 50px;
}
.voice-unit {
width: 60px;
height: 60px;
background:
radial-gradient(circle at 40% 40%, #4a5a6a 0%, #2a3545 70%);
border: 3px solid var(--metal-light);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.voice-unit:hover {
border-color: var(--accent-cyan);
transform: scale(1.1);
}
.voice-unit.active {
border-color: var(--accent-green);
box-shadow: 0 0 15px rgba(72, 187, 120, 0.4);
}
.voice-unit.anomaly {
border-color: var(--accent-orange);
box-shadow: 0 0 15px rgba(237, 137, 54, 0.4);
}
.voice-unit .speaker-icon {
width: 24px;
height: 24px;
background:
radial-gradient(circle, #1a2535 30%, #3a4a5a 70%);
border-radius: 50%;
border: 2px solid #5a6a7a;
}
.voice-unit-label {
font-size: 0.65em;
color: var(--text-dim);
margin-top: 5px;
}
/* ============ 右侧面板 ============ */
.side-panel {
background: linear-gradient(180deg, #1a2030 0%, #0f1520 100%);
border-left: 3px solid var(--metal-dark);
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
overflow-y: auto;
}
.panel-card {
background: rgba(255,255,255,0.03);
border: 1px solid var(--metal-dark);
border-radius: 8px;
padding: 15px;
}
.panel-card-title {
font-size: 0.85em;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid var(--metal-dark);
}
/* 患者信息 */
.patient-info {
display: flex;
flex-direction: column;
gap: 8px;
}
.patient-name {
font-family: 'Orbitron', sans-serif;
font-size: 1.1em;
color: var(--accent-orange);
}
.patient-status {
font-size: 0.9em;
color: var(--accent-red);
}
/* 阶段进度 */
.phase-progress {
display: flex;
flex-direction: column;
gap: 8px;
}
.phase-item {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 0;
color: var(--text-dim);
font-size: 0.9em;
}
.phase-item.active {
color: var(--accent-cyan);
}
.phase-item.completed {
color: var(--accent-green);
}
.phase-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--metal-dark);
border: 1px solid var(--metal-light);
}
.phase-item.active .phase-dot {
background: var(--accent-cyan);
box-shadow: 0 0 8px var(--accent-cyan);
}
.phase-item.completed .phase-dot {
background: var(--accent-green);
}
/* 数据显示 */
.data-display {
display: flex;
flex-direction: column;
gap: 8px;
}
.data-row {
display: flex;
justify-content: space-between;
font-size: 0.9em;
}
.data-label {
color: var(--text-dim);
}
.data-value {
color: var(--text-normal);
}
.data-value.normal { color: var(--accent-green); }
.data-value.warning { color: var(--accent-orange); }
.data-value.error { color: var(--accent-red); }
/* 波形显示 */
.waveform-display {
height: 50px;
background: #0a0f15;
border: 1px solid var(--metal-dark);
border-radius: 4px;
position: relative;
overflow: hidden;
}
.waveform-display canvas {
width: 100%;
height: 100%;
}
/* 信号流向图 */
.signal-flow {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px;
background: #0a0f15;
border-radius: 6px;
}
.signal-node {
padding: 6px 12px;
border-radius: 4px;
font-size: 0.8em;
}
.signal-node.input {
background: rgba(99, 179, 237, 0.2);
border: 1px solid var(--accent-blue);
color: var(--accent-blue);
}
.signal-node.process {
background: rgba(237, 137, 54, 0.2);
border: 1px solid var(--accent-orange);
color: var(--accent-orange);
}
.signal-node.output {
background: rgba(252, 129, 129, 0.2);
border: 1px solid var(--accent-red);
color: var(--accent-red);
}
.signal-arrow {
color: var(--text-dim);
}
/* 日志面板 */
.log-panel {
flex: 1;
min-height: 120px;
background: #0a0f15;
border: 1px solid var(--metal-dark);
border-radius: 6px;
padding: 10px;
font-size: 0.85em;
overflow-y: auto;
}
.log-entry {
padding: 4px 0;
border-bottom: 1px solid rgba(255,255,255,0.05);
color: var(--text-dim);
}
.log-entry.info { color: var(--accent-cyan); }
.log-entry.warning { color: var(--accent-orange); }
.log-entry.error { color: var(--accent-red); }
.log-entry.success { color: var(--accent-green); }
.log-time {
color: #4a5568;
margin-right: 6px;
}
/* 操作按钮 */
.action-btn {
width: 100%;
padding: 12px;
background: linear-gradient(145deg, #2a4a5a, #1a3a4a);
border: 2px solid var(--accent-cyan);
border-radius: 6px;
color: var(--accent-cyan);
font-family: inherit;
font-size: 1em;
cursor: pointer;
transition: all 0.2s;
}
.action-btn:hover:not(:disabled) {
background: linear-gradient(145deg, #3a5a6a, #2a4a5a);
box-shadow: 0 0 20px rgba(100, 255, 218, 0.2);
}
.action-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* ============ 对话框 ============ */
.dialog-overlay {
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: 3px solid var(--metal-dark);
transform: translateY(100%);
transition: transform 0.3s ease;
z-index: 200;
}
.dialog-overlay.show {
transform: translateY(0);
}
.dialog-speaker {
font-family: 'Orbitron', sans-serif;
font-size: 0.9em;
color: var(--accent-cyan);
margin-bottom: 8px;
}
.dialog-speaker.huoshan {
color: var(--accent-orange);
}
.dialog-text {
font-size: 1.15em;
line-height: 1.6;
color: var(--text-normal);
max-width: 900px;
}
.dialog-continue {
position: absolute;
right: 30px;
bottom: 20px;
color: var(--text-dim);
font-size: 0.85em;
animation: blink 1s ease-in-out infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* ============ 启动界面 ============ */
.boot-screen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, #0a0f1a 0%, #1a2535 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
transition: opacity 0.5s;
}
.boot-screen.hidden {
opacity: 0;
pointer-events: none;
}
.boot-logo {
font-family: 'Orbitron', sans-serif;
font-size: 2.5em;
color: var(--accent-cyan);
margin-bottom: 15px;
text-shadow: 0 0 30px rgba(100, 255, 218, 0.5);
}
.boot-subtitle {
color: var(--text-dim);
margin-bottom: 40px;
letter-spacing: 2px;
}
.boot-btn {
padding: 15px 50px;
background: linear-gradient(145deg, #1a3a4a, #0f2a3a);
border: 2px solid var(--accent-cyan);
border-radius: 8px;
color: var(--accent-cyan);
font-family: 'Orbitron', sans-serif;
font-size: 1.1em;
cursor: pointer;
transition: all 0.3s;
}
.boot-btn:hover {
background: linear-gradient(145deg, #2a4a5a, #1a3a4a);
box-shadow: 0 0 30px rgba(100, 255, 218, 0.3);
transform: translateY(-2px);
}
/* 隐藏 */
.hidden {
display: none !important;
}
/* ============ 粒子游戏区域 ============ */
.particle-game-area {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(ellipse at center, #0a1520 0%, #050a10 100%);
display: none;
}
.particle-game-area.active {
display: block;
}
.particle-arena {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 500px;
height: 400px;
background: rgba(10, 20, 30, 0.8);
border: 3px solid var(--metal-light);
border-radius: 50%;
overflow: hidden;
}
.particle {
position: absolute;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
font-weight: bold;
cursor: default;
user-select: none;
}
.particle.target {
background: radial-gradient(circle, #2a6a4a 0%, #1a4a3a 100%);
border: 2px solid var(--accent-green);
color: #a0ffda;
box-shadow: 0 0 15px rgba(72, 187, 120, 0.4);
}
.particle.non-target {
background: radial-gradient(circle, #6a3a3a 0%, #4a2a2a 100%);
border: 2px solid var(--accent-red);
color: #ffa0a0;
box-shadow: 0 0 15px rgba(252, 129, 129, 0.4);
}
.game-ui-top {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
.game-instruction {
background: rgba(0,0,0,0.7);
padding: 10px 20px;
border-radius: 6px;
color: var(--text-dim);
margin-bottom: 10px;
}
.progress-bar-container {
width: 300px;
height: 12px;
background: rgba(255,255,255,0.1);
border-radius: 6px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent-cyan), var(--accent-green));
transition: width 0.3s;
}
</style>
</head>
<body>
<!-- 启动界面 -->
<div class="boot-screen" id="bootScreen">
<div class="boot-logo">🌋 火山修复流程</div>
<div class="boot-subtitle">表达模块硬件诊断系统</div>
<button class="boot-btn" onclick="startGame()">开始诊断</button>
</div>
<!-- 主界面 -->
<div class="game-container">
<!-- 主视图区域 -->
<div class="main-view">
<div class="scanlines"></div>
<!-- 机器人后脑视图 -->
<div class="brain-view" id="brainView">
<div class="brain-shell">
<div class="brain-frame">
<!-- 信息标签 -->
<div class="info-tag tag-memory">MEMORY</div>
<div class="info-tag tag-extension">EXTENSION</div>
<div class="info-tag tag-vision">VISION</div>
<!-- 中央处理器 -->
<div class="central-processor">
<div class="processor-inner"></div>
</div>
<!-- 连接线 -->
<div class="connection-line line-emotion-center"></div>
<div class="connection-line line-logic-center"></div>
<div class="connection-line line-center-expression"></div>
<!-- 情绪模块 -->
<div class="brain-module module-emotion" id="moduleEmotion" onclick="clickModule('emotion')">
<div class="module-led" id="ledEmotion"></div>
<div class="module-label">
情绪
<div class="module-connector"></div>
</div>
</div>
<!-- 逻辑模块 -->
<div class="brain-module module-logic" id="moduleLogic" onclick="clickModule('logic')">
<div class="module-led" id="ledLogic"></div>
<div class="module-label">
逻辑
<div class="module-connector"></div>
</div>
</div>
<!-- 表达模块 -->
<div class="brain-module module-expression" id="moduleExpression" onclick="clickModule('expression')">
<div class="module-led warning" id="ledExpression"></div>
<div class="module-label">
表达
<div class="module-connector"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 表达模块详细视图 -->
<div class="expression-detail-view" id="expressionDetail">
<div class="detail-header">
<div class="detail-title">📦 表达模块 - 内部结构</div>
<button class="back-btn" onclick="backToBrain()">← 返回</button>
</div>
<div class="hardware-layout">
<!-- 识别器 -->
<div class="recognizer-unit" id="recognizer">
<div class="unit-name">识别器</div>
<div class="unit-status" id="recognizerStatus">接收情绪信号</div>
</div>
<div class="flow-arrow"></div>
<!-- 处理单元 -->
<div class="processing-container">
<div class="processing-title">语言处理单元</div>
<div class="processing-grid" id="processingGrid">
<!-- 动态生成 -->
</div>
</div>
<div class="flow-arrow"></div>
<!-- 生成器 -->
<div class="generator-unit" id="generator">
<div class="unit-name">生成器</div>
<div class="unit-status" id="generatorStatus">组合语言输出</div>
</div>
<div class="flow-arrow"></div>
<!-- 发声单元 -->
<div class="voice-units-container" id="voiceUnits">
<!-- 动态生成 -->
</div>
</div>
</div>
<!-- 粒子游戏区域 -->
<div class="particle-game-area" id="particleGameArea">
<div class="game-ui-top">
<div class="game-instruction" id="gameInstruction">
左键排斥粒子 | 右键吸引粒子 | 让绿色粒子连接
</div>
<div class="progress-bar-container">
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
</div>
</div>
<div class="particle-arena" id="particleArena">
<svg id="connectionsSvg" style="position:absolute;width:100%;height:100%;pointer-events:none;"></svg>
<div id="particlesContainer"></div>
</div>
</div>
</div>
<!-- 右侧面板 -->
<div class="side-panel">
<!-- 患者信息 -->
<div class="panel-card">
<div class="panel-card-title">患者信息</div>
<div class="patient-info">
<div class="patient-name">🤖 火山 HUOSHAN-001</div>
<div class="patient-status" id="patientStatus">状态:表达模块异常</div>
</div>
</div>
<!-- 诊断进度 -->
<div class="panel-card">
<div class="panel-card-title">诊断进度</div>
<div class="phase-progress">
<div class="phase-item active" id="phase1">
<div class="phase-dot"></div>
<span>问诊</span>
</div>
<div class="phase-item" id="phase2">
<div class="phase-dot"></div>
<span>发声检查</span>
</div>
<div class="phase-item" id="phase3">
<div class="phase-dot"></div>
<span>情绪检查</span>
</div>
<div class="phase-item" id="phase4">
<div class="phase-dot"></div>
<span>滤波器定位</span>
</div>
<div class="phase-item" id="phase5">
<div class="phase-dot"></div>
<span>清理治疗</span>
</div>
<div class="phase-item" id="phase6">
<div class="phase-dot"></div>
<span>修复完成</span>
</div>
</div>
</div>
<!-- 信号流向 -->
<div class="panel-card" id="signalFlowCard" style="display: none;">
<div class="panel-card-title">信号流向</div>
<div class="signal-flow">
<div class="signal-node input" id="signalInput">😰 恐惧</div>
<div class="signal-arrow"></div>
<div class="signal-node process" id="signalProcess">⚡ 转换</div>
<div class="signal-arrow"></div>
<div class="signal-node output" id="signalOutput">😂 笑话</div>
</div>
</div>
<!-- 采集数据 -->
<div class="panel-card">
<div class="panel-card-title">采集数据</div>
<div class="data-display">
<div class="data-row">
<span class="data-label">情绪波形</span>
<span class="data-value" id="dataEmotion">--</span>
</div>
<div class="data-row">
<span class="data-label">语调参数</span>
<span class="data-value" id="dataVoice">--</span>
</div>
<div class="data-row">
<span class="data-label">滤波器</span>
<span class="data-value" id="dataFilter">--</span>
</div>
</div>
<div class="waveform-display" style="margin-top: 10px;">
<canvas id="miniWaveform"></canvas>
</div>
</div>
<!-- 系统日志 -->
<div class="panel-card" style="flex: 1; display: flex; flex-direction: column;">
<div class="panel-card-title">系统日志</div>
<div class="log-panel" id="logPanel">
<div class="log-entry info">
<span class="log-time">[00:00]</span>诊断系统就绪
</div>
</div>
</div>
<!-- 操作按钮 -->
<button class="action-btn" id="actionBtn" onclick="doAction()">
开始诊断
</button>
</div>
</div>
<!-- 对话框 -->
<div class="dialog-overlay" id="dialogOverlay">
<div class="dialog-speaker" id="dialogSpeaker">医生</div>
<div class="dialog-text" id="dialogText">内容</div>
<div class="dialog-continue">点击继续 ▶</div>
</div>
<script>
// ============ 游戏状态 ============
const state = {
currentPhase: 1,
currentView: 'brain', // brain, expression, game
emotionChecked: false,
voiceChecked: false,
filterFound: false,
gameRound: 0,
particles: [],
isGameRunning: false,
mousePos: { x: 0, y: 0 },
isMouseDown: false,
mouseButton: 0
};
// 处理单元数据
const processingUnits = [
{ id: 0, name: '恐惧', icon: '😰', active: true },
{ id: 1, name: '焦虑', icon: '😟', active: true },
{ id: 2, name: '悲伤', icon: '😢', active: false },
{ id: 3, name: '愤怒', icon: '😠', active: false },
{ id: 4, name: '无奈', icon: '😔', active: false },
{ id: 5, name: '幽默', icon: '😄', active: false, anomaly: true },
{ id: 6, name: '讽刺', icon: '😏', active: false, anomaly: true },
{ id: 7, name: '调侃', icon: '🤪', active: false, anomaly: true },
{ id: 8, name: '玩笑', icon: '😂', active: false, anomaly: true },
{ id: 9, name: '回避', icon: '😅', active: false, anomaly: true }
];
// 对话队列
let dialogQueue = [];
let dialogCallback = null;
// ============ 初始化 ============
function init() {
initProcessingUnits();
initVoiceUnits();
initWaveform();
}
function initProcessingUnits() {
const grid = document.getElementById('processingGrid');
grid.innerHTML = '';
processingUnits.forEach(unit => {
const div = document.createElement('div');
div.className = 'processing-unit' +
(unit.active ? ' active' : '') +
(unit.anomaly ? ' anomaly' : '');
div.id = 'unit-' + unit.id;
div.innerHTML = `
<div class="unit-icon">${unit.icon}</div>
<div class="unit-label">${unit.name}</div>
`;
div.onclick = () => clickProcessingUnit(unit.id);
grid.appendChild(div);
});
}
function initVoiceUnits() {
const container = document.getElementById('voiceUnits');
container.innerHTML = '';
for (let i = 0; i < 6; i++) {
const div = document.createElement('div');
div.className = 'voice-unit' + (i === 2 ? ' anomaly' : '');
div.id = 'voice-' + i;
div.innerHTML = `
<div class="speaker-icon"></div>
`;
div.title = 'V0' + (i + 1);
container.appendChild(div);
}
}
let waveformAnim = null;
function initWaveform() {
const canvas = document.getElementById('miniWaveform');
const ctx = canvas.getContext('2d');
canvas.width = canvas.parentElement.clientWidth;
canvas.height = 50;
let phase = 0;
waveformAnim = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#64ffda';
ctx.lineWidth = 1.5;
ctx.beginPath();
for (let x = 0; x < canvas.width; x++) {
const y = 25 +
Math.sin((x + phase) * 0.1) * 10 +
Math.sin((x + phase) * 0.2) * 5 +
(Math.random() - 0.5) * 3;
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
phase += 2;
}, 50);
}
// ============ 游戏启动 ============
function startGame() {
document.getElementById('bootScreen').classList.add('hidden');
init();
addLog('info', '患者火山进入诊疗室');
showDialog([
{ speaker: "系统", text: "患者 HUOSHAN-001 已连接。检测到表达模块异常信号。" },
{ speaker: "火山", text: "嘿,医——医生!你知道、知道销售机器人最怕什么吗?" },
{ speaker: "火山", text: "打——打折季!因为我们的价值也——哈哈,开玩笑的!" },
{ speaker: "医生", text: "火山,你说话有点结巴...让我检查一下你的表达模块。" },
{ speaker: "医生", text: "点击后脑的「表达」模块开始检查。" }
]);
}
// ============ 模块点击 ============
function clickModule(module) {
if (module === 'expression') {
showExpressionDetail();
} else if (module === 'emotion') {
addLog('info', '情绪模块:检测到恐惧/焦虑信号');
showDialog([
{ speaker: "医生", text: "情绪模块显示高强度的恐惧和焦虑信号..." },
{ speaker: "医生", text: "这和火山说的笑话完全不符。问题出在表达模块。" }
]);
} else if (module === 'logic') {
addLog('info', '逻辑模块:运行正常');
}
}
function showExpressionDetail() {
document.getElementById('brainView').style.display = 'none';
document.getElementById('expressionDetail').classList.add('active');
state.currentView = 'expression';
addLog('info', '进入表达模块内部视图');
updatePhase(2);
showDialog([
{ speaker: "医生", text: "这是表达模块的内部结构。" },
{ speaker: "医生", text: "信号从识别器进入,分发到语言处理单元,最后通过发声单元输出。" },
{ speaker: "医生", text: "让我检查一下各个单元的状态..." }
], () => {
setTimeout(() => analyzeUnits(), 500);
});
}
function backToBrain() {
document.getElementById('expressionDetail').classList.remove('active');
document.getElementById('brainView').style.display = 'block';
state.currentView = 'brain';
}
// ============ 分析单元 ============
function analyzeUnits() {
addLog('info', '扫描处理单元...');
// 依次高亮单元
let i = 0;
const interval = setInterval(() => {
if (i > 0) {
document.getElementById('unit-' + (i-1)).style.transform = '';
}
if (i < processingUnits.length) {
document.getElementById('unit-' + i).style.transform = 'scale(1.1)';
i++;
} else {
clearInterval(interval);
addLog('warning', '发现异常:幽默相关单元过度激活!');
document.getElementById('signalFlowCard').style.display = 'block';
document.getElementById('dataEmotion').textContent = '恐惧 89%';
document.getElementById('dataEmotion').className = 'data-value error';
document.getElementById('dataVoice').textContent = '语调异常';
document.getElementById('dataVoice').className = 'data-value warning';
showDialog([
{ speaker: "医生", text: "发现问题了!上排是情绪处理单元,正在接收恐惧信号..." },
{ speaker: "医生", text: "但下排的幽默单元异常激活,劫持了输出!" },
{ speaker: "医生", text: "有个滤波器把所有负面情绪都转换成了笑话。" },
{ speaker: "火山", text: "我一直都是这样啊...用笑话比较轻松..." },
{ speaker: "医生", text: "火山,那些真实的情绪被堆积了。我需要帮你清理它们。" }
], () => {
state.filterFound = true;
document.getElementById('dataFilter').textContent = '转换率 95%';
document.getElementById('dataFilter').className = 'data-value error';
updatePhase(4);
document.getElementById('actionBtn').textContent = '开始清理治疗';
});
}
}, 150);
}
// ============ 处理单元点击 ============
function clickProcessingUnit(id) {
const unit = processingUnits[id];
if (unit.anomaly) {
addLog('warning', `单元 [${unit.name}] 异常激活中`);
} else if (unit.active) {
addLog('info', `单元 [${unit.name}] 正在处理信号`);
}
}
// ============ 主操作按钮 ============
function doAction() {
if (!state.filterFound) {
clickModule('expression');
} else if (state.gameRound === 0) {
startCleaningGame(1);
}
}
// ============ 清理游戏 ============
function startCleaningGame(round) {
state.gameRound = round;
state.isGameRunning = true;
state.currentView = 'game';
document.getElementById('expressionDetail').classList.remove('active');
document.getElementById('brainView').style.display = 'none';
document.getElementById('particleGameArea').classList.add('active');
updatePhase(5);
const arena = document.getElementById('particleArena');
const container = document.getElementById('particlesContainer');
const rect = arena.getBoundingClientRect();
container.innerHTML = '';
state.particles = [];
document.getElementById('progressFill').style.width = '0%';
const targetChars = [
['我', '担', '心', '...'],
['我', '害', '怕', '...'],
['我', '害', '怕', '被', '抛', '弃']
];
const nonTargetChars = ['哈', '笑', '话', '嘿', '呵', '嘻'];
const chars = targetChars[round - 1] || targetChars[0];
const targetCount = chars.length;
const nonTargetCount = 6;
// 创建目标粒子
for (let i = 0; i < targetCount; i++) {
const particle = {
id: i,
isTarget: true,
x: Math.random() * (rect.width - 80) + 15,
y: Math.random() * (rect.height - 80) + 15,
vx: (Math.random() - 0.5) * 1.5,
vy: (Math.random() - 0.5) * 1.5,
char: chars[i]
};
state.particles.push(particle);
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.id = 'p-' + particle.id;
container.appendChild(el);
}
// 创建干扰粒子
for (let i = 0; i < nonTargetCount; i++) {
const particle = {
id: targetCount + i,
isTarget: false,
x: Math.random() * (rect.width - 80) + 15,
y: Math.random() * (rect.height - 80) + 15,
vx: (Math.random() - 0.5) * 1.5,
vy: (Math.random() - 0.5) * 1.5,
char: nonTargetChars[i % nonTargetChars.length]
};
state.particles.push(particle);
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.id = 'p-' + particle.id;
container.appendChild(el);
}
addLog('info', `开始第${round}次清理...`);
requestAnimationFrame(gameLoop);
}
// 游戏循环
function gameLoop() {
if (!state.isGameRunning) return;
const arena = document.getElementById('particleArena');
const rect = arena.getBoundingClientRect();
state.particles.forEach(particle => {
particle.x += particle.vx;
particle.y += particle.vy;
// 椭圆边界
const cx = rect.width / 2;
const cy = rect.height / 2;
const rx = rect.width / 2 - 30;
const ry = rect.height / 2 - 30;
const dx = (particle.x + 25 - cx) / rx;
const dy = (particle.y + 25 - cy) / ry;
const dist = dx * dx + dy * dy;
if (dist > 0.85) {
const angle = Math.atan2(dy, dx);
particle.vx -= Math.cos(angle) * 0.5;
particle.vy -= Math.sin(angle) * 0.5;
}
// 鼠标交互
if (state.isMouseDown) {
const mx = state.mousePos.x - rect.left - 25;
const my = state.mousePos.y - rect.top - 25;
const pdx = particle.x - mx;
const pdy = particle.y - my;
const pdist = Math.hypot(pdx, pdy);
if (pdist < 80 && pdist > 0) {
const force = (80 - pdist) / 80;
if (state.mouseButton === 0) {
particle.vx += (pdx / pdist) * force * 2;
particle.vy += (pdy / pdist) * force * 2;
} else if (state.mouseButton === 2) {
particle.vx -= (pdx / pdist) * force * 1.2;
particle.vy -= (pdy / pdist) * force * 1.2;
}
}
}
particle.vx *= 0.98;
particle.vy *= 0.98;
const el = document.getElementById('p-' + particle.id);
if (el) {
el.style.left = particle.x + 'px';
el.style.top = particle.y + 'px';
}
});
drawConnections();
const progress = calculateProgress();
document.getElementById('progressFill').style.width = progress + '%';
if (progress >= 100) {
completeGame();
} else {
requestAnimationFrame(gameLoop);
}
}
function calculateProgress() {
const targets = state.particles.filter(p => p.isTarget);
const nonTargets = state.particles.filter(p => !p.isTarget);
if (targets.length < 2) return 100;
const connectionDist = 70;
const visited = new Set([targets[0].id]);
const queue = [targets[0]];
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 connectivity = visited.size / targets.length;
let separated = true;
for (const t of targets) {
for (const nt of nonTargets) {
if (Math.hypot(t.x - nt.x, t.y - nt.y) < connectionDist) {
separated = false;
break;
}
}
if (!separated) break;
}
const progress = connectivity * 70 + (separated ? 30 : 0);
return (connectivity >= 1 && separated) ? 100 : Math.min(99, progress);
}
function drawConnections() {
const svg = document.getElementById('connectionsSvg');
svg.innerHTML = '';
const targets = state.particles.filter(p => p.isTarget);
const connectionDist = 70;
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 < connectionDist) {
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', targets[i].x + 25);
line.setAttribute('y1', targets[i].y + 25);
line.setAttribute('x2', targets[j].x + 25);
line.setAttribute('y2', targets[j].y + 25);
line.setAttribute('stroke', 'rgba(72, 187, 120, 0.6)');
line.setAttribute('stroke-width', '2');
line.style.opacity = 1 - dist / connectionDist;
svg.appendChild(line);
}
}
}
}
function completeGame() {
state.isGameRunning = false;
const sentences = ['我担心...', '我害怕...', '我害怕被抛弃...'];
addLog('success', `火山说出了:"${sentences[state.gameRound - 1]}"`);
if (state.gameRound < 3) {
showDialog([
{ speaker: "火山", text: `"${sentences[state.gameRound - 1]}"` },
{ speaker: "医生", text: "很好!继续,说出更多。" }
], () => {
setTimeout(() => startCleaningGame(state.gameRound + 1), 500);
});
} else {
// 完成
showDialog([
{ speaker: "火山", text: `"${sentences[2]}"` },
{ speaker: "火山", text: "我害怕如果不有趣,就没人要我了..." },
{ speaker: "火山", text: "说出来之后...好像没那么可怕了。" },
{ speaker: "医生", text: "你做到了,火山。这才是真正的你。" },
{ speaker: "火山", text: "谢谢你愿意听我说真话..." }
], () => {
updatePhase(6);
document.getElementById('patientStatus').textContent = '状态:表达通路已修复';
document.getElementById('patientStatus').style.color = 'var(--accent-green)';
document.getElementById('ledExpression').className = 'module-led active';
document.getElementById('particleGameArea').classList.remove('active');
document.getElementById('brainView').style.display = 'block';
addLog('success', '治疗完成!真实表达通路已建立。');
});
}
}
// ============ 阶段更新 ============
function updatePhase(phase) {
state.currentPhase = phase;
for (let i = 1; i <= 6; i++) {
const el = document.getElementById('phase' + i);
el.classList.remove('active', 'completed');
if (i < phase) el.classList.add('completed');
if (i === phase) el.classList.add('active');
}
}
// ============ 对话系统 ============
function showDialog(dialogs, callback = null) {
dialogQueue = [...dialogs];
dialogCallback = callback;
nextDialog();
}
function nextDialog() {
if (dialogQueue.length === 0) {
document.getElementById('dialogOverlay').classList.remove('show');
if (dialogCallback) {
const cb = dialogCallback;
dialogCallback = null;
cb();
}
return;
}
const dialog = dialogQueue.shift();
const speakerEl = document.getElementById('dialogSpeaker');
speakerEl.textContent = dialog.speaker;
speakerEl.className = 'dialog-speaker' + (dialog.speaker === '火山' ? ' huoshan' : '');
document.getElementById('dialogText').textContent = dialog.text;
document.getElementById('dialogOverlay').classList.add('show');
}
document.getElementById('dialogOverlay').addEventListener('click', nextDialog);
// ============ 日志 ============
function addLog(type, message) {
const panel = document.getElementById('logPanel');
const time = new Date().toLocaleTimeString('zh-CN', {
hour: '2-digit', minute: '2-digit', second: '2-digit'
});
const entry = document.createElement('div');
entry.className = 'log-entry ' + type;
entry.innerHTML = `<span class="log-time">[${time}]</span>${message}`;
panel.appendChild(entry);
panel.scrollTop = panel.scrollHeight;
}
// ============ 鼠标事件 ============
const arena = document.getElementById('particleArena');
arena.addEventListener('mousedown', (e) => {
state.isMouseDown = true;
state.mouseButton = e.button;
state.mousePos = { x: e.clientX, y: e.clientY };
});
document.addEventListener('mouseup', () => {
state.isMouseDown = false;
});
arena.addEventListener('mousemove', (e) => {
state.mousePos = { x: e.clientX, y: e.clientY };
});
arena.addEventListener('contextmenu', (e) => e.preventDefault());
</script>
</body>
</html>