879 lines
31 KiB
HTML
879 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>情绪数据析出终端 V10 - 终极形态</title>
|
|
<style>
|
|
:root {
|
|
--c-main: #00ffcc;
|
|
--c-dim: #004433;
|
|
--c-alert: #ff3333;
|
|
--c-pure: #ffffff;
|
|
--bg: #050505;
|
|
}
|
|
|
|
body {
|
|
background-color: #000;
|
|
color: var(--c-main);
|
|
font-family: 'Courier New', monospace;
|
|
margin: 0;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
display: grid;
|
|
grid-template-columns: 1fr 380px;
|
|
grid-template-rows: 1fr 180px;
|
|
user-select: none;
|
|
}
|
|
|
|
/* 1. 主池 */
|
|
#tank-area {
|
|
grid-column: 1 / 2; grid-row: 1 / 2;
|
|
position: relative;
|
|
background: radial-gradient(circle at 50% 50%, #0a1a1a 0%, #000 90%);
|
|
border-right: 1px solid #333;
|
|
border-bottom: 1px solid #333;
|
|
overflow: hidden;
|
|
cursor: crosshair;
|
|
transition: background 1s;
|
|
}
|
|
#tank-area.purified {
|
|
background: radial-gradient(circle at 50% 50%, #0a2a2a 0%, #000 90%);
|
|
}
|
|
|
|
#tank-area.sampling {
|
|
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%23ff3333" stroke-width="2"><circle cx="12" cy="12" r="8"/><line x1="12" y1="4" x2="12" y2="20"/><line x1="4" y1="12" x2="20" y2="12"/></svg>') 12 12, crosshair;
|
|
}
|
|
|
|
/* 2. 右侧面板 */
|
|
#scope-panel {
|
|
grid-column: 2 / 3; grid-row: 1 / 2;
|
|
background: #080808; border-bottom: 1px solid #333;
|
|
display: flex; flex-direction: column;
|
|
padding: 15px; gap: 15px;
|
|
box-shadow: inset 10px 0 20px rgba(0,0,0,0.5);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.panel-group {
|
|
display: flex; flex-direction: column; gap: 5px;
|
|
background: #020202; border: 1px solid #222; padding: 10px;
|
|
}
|
|
.label { font-size: 10px; color: #666; letter-spacing: 1px; font-weight: bold; margin-bottom: 5px; }
|
|
|
|
/* A. 示波器 */
|
|
#wave-monitor {
|
|
height: 100px; background: #000500;
|
|
border: 1px solid #333;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: all 0.5s;
|
|
}
|
|
/* 调准后的发光效果 */
|
|
#wave-monitor.locked {
|
|
border-color: var(--c-main);
|
|
box-shadow: 0 0 20px rgba(0, 255, 204, 0.15);
|
|
background: #001a15;
|
|
}
|
|
|
|
.scope-grid {
|
|
position: absolute; top:0; left:0; width:100%; height:100%;
|
|
background-image:
|
|
linear-gradient(rgba(0, 255, 204, 0.05) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(0, 255, 204, 0.05) 1px, transparent 1px);
|
|
background-size: 20px 20px;
|
|
pointer-events: none;
|
|
}
|
|
#waveCanvas { width: 100%; height: 100%; position: relative; z-index: 1; }
|
|
|
|
/* B. 参数数值 */
|
|
.data-row {
|
|
display: flex; justify-content: space-between;
|
|
font-size: 12px; border-bottom: 1px solid #111; padding: 4px 0;
|
|
color: #555;
|
|
}
|
|
.data-row.matched {
|
|
color: var(--c-main);
|
|
text-shadow: 0 0 8px rgba(0, 255, 204, 0.4);
|
|
}
|
|
|
|
/* C. 缓冲区 (进度条) */
|
|
.buffer-track {
|
|
height: 10px; background: #111; border: 1px solid #444;
|
|
margin-top: 5px; position: relative;
|
|
}
|
|
.buffer-fill {
|
|
height: 100%; width: 0%; background: var(--c-alert);
|
|
transition: width 0.2s;
|
|
box-shadow: 0 0 8px var(--c-alert);
|
|
}
|
|
.buffer-text {
|
|
text-align: right; font-size: 10px; color: #666; margin-top: 4px;
|
|
}
|
|
|
|
/* D. 分析图容器 */
|
|
#analysis-wrapper {
|
|
flex: 1; display: flex; flex-direction: column; gap: 10px;
|
|
display: none; /* 初始隐藏 */
|
|
}
|
|
|
|
#analysis-container {
|
|
height: 200px;
|
|
border: 1px solid #333;
|
|
background: #000;
|
|
position: relative;
|
|
}
|
|
.axis-txt {
|
|
position: absolute; font-size: 9px; color: #444; pointer-events: none;
|
|
}
|
|
|
|
/* E. 药剂控制区 */
|
|
#reagent-controls {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|
grid-template-rows: 1fr 1fr;
|
|
gap: 5px;
|
|
height: 80px;
|
|
}
|
|
.reagent-btn {
|
|
background: #111; border: 1px solid #333;
|
|
color: #666; font-family: inherit; font-size: 10px;
|
|
cursor: pointer; transition: 0.2s;
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
}
|
|
.reagent-btn:hover { border-color: #666; color: #fff; }
|
|
.reagent-btn:active { background: #222; }
|
|
|
|
/* 布局: 上下左右 */
|
|
#btn-up { grid-column: 2; grid-row: 1; }
|
|
#btn-left { grid-column: 1; grid-row: 2; }
|
|
#btn-right { grid-column: 3; grid-row: 2; }
|
|
#btn-down { grid-column: 2; grid-row: 2; }
|
|
|
|
.chem-icon { width: 8px; height: 12px; border: 1px solid #555; margin-bottom: 2px; position: relative; }
|
|
.chem-fill { position: absolute; bottom: 0; width: 100%; height: 50%; background: #444; transition: 0.3s; }
|
|
.reagent-btn:hover .chem-fill { height: 80%; background: var(--c-main); box-shadow: 0 0 5px var(--c-main); }
|
|
|
|
#btn-inject {
|
|
width: 100%; height: 40px;
|
|
margin-top: 10px;
|
|
background: #111; border: 1px solid #333; color: #444;
|
|
font-weight: bold; letter-spacing: 2px;
|
|
cursor: not-allowed; display: none;
|
|
}
|
|
#btn-inject.ready {
|
|
background: var(--c-main); color: #000; border-color: var(--c-main);
|
|
box-shadow: 0 0 15px var(--c-main); cursor: pointer;
|
|
animation: pulseInject 1s infinite;
|
|
}
|
|
@keyframes pulseInject { 50% { box-shadow: 0 0 25px var(--c-main); } }
|
|
|
|
|
|
/* 3. 底部控制 */
|
|
#control-panel {
|
|
grid-column: 1 / 3; grid-row: 2 / 3;
|
|
background: #0c0c0c; border-top: 1px solid #333;
|
|
display: flex; align-items: center; padding: 0 40px; gap: 40px;
|
|
}
|
|
|
|
.slider-box { flex: 1; }
|
|
.slider-head { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 10px; color: #888; }
|
|
|
|
input[type=range] { width: 100%; cursor: pointer; }
|
|
|
|
#btn-main {
|
|
width: 160px; height: 60px;
|
|
background: #111; border: 2px solid #333; color: #555;
|
|
font-family: inherit; font-weight: bold; letter-spacing: 2px;
|
|
transition: 0.3s; cursor: not-allowed;
|
|
}
|
|
#btn-main.ready {
|
|
border-color: var(--c-main); color: var(--c-main);
|
|
box-shadow: 0 0 15px rgba(0, 255, 204, 0.2); cursor: pointer;
|
|
}
|
|
#btn-main.active {
|
|
background: var(--c-alert); border-color: #f00; color: #000;
|
|
box-shadow: 0 0 30px rgba(255, 0, 0, 0.4);
|
|
animation: pulseBtn 1s infinite;
|
|
}
|
|
@keyframes pulseBtn { 50% { box-shadow: 0 0 10px rgba(255,0,0,0.2); } }
|
|
|
|
/* 浮动文字 */
|
|
.pop-text {
|
|
position: absolute; pointer-events: none; color: #fff; font-size: 12px; font-weight: bold;
|
|
animation: flyUp 1s forwards; z-index: 100;
|
|
}
|
|
@keyframes flyUp { to { transform: translateY(-30px); opacity: 0; } }
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- 主画面 -->
|
|
<div id="tank-area">
|
|
<div style="position:absolute; top:15px; left:20px; z-index:10;">
|
|
<div style="font-size:10px; color:#444;">SYSTEM STATUS</div>
|
|
<div id="sys-status" style="font-size:14px; color:#666; margin-top:4px;">STANDBY</div>
|
|
</div>
|
|
<canvas id="mainCanvas"></canvas>
|
|
</div>
|
|
|
|
<!-- 右侧信息 -->
|
|
<div id="scope-panel">
|
|
|
|
<!-- 1. 示波器 -->
|
|
<div class="panel-group">
|
|
<div class="label">SIGNAL WAVEFORM</div>
|
|
<div id="wave-monitor">
|
|
<div class="scope-grid"></div>
|
|
<canvas id="waveCanvas"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 2. 数据监视 -->
|
|
<div class="panel-group">
|
|
<div class="label">RESONANCE METRICS</div>
|
|
<div class="data-row" id="row-0"><span>FREQUENCY</span> <span id="txt-0">00</span></div>
|
|
<div class="data-row" id="row-1"><span>NOISE_LVL</span> <span id="txt-1">00</span></div>
|
|
<div class="data-row" id="row-2"><span>AMPLITUDE</span> <span id="txt-2">00</span></div>
|
|
</div>
|
|
|
|
<!-- 3. 样本/分析结果 -->
|
|
<div class="panel-group" style="flex:1; display:flex; flex-direction:column;">
|
|
<div class="label" style="display:flex; justify-content:space-between;">
|
|
<span>ANALYSIS & MODULATION</span>
|
|
</div>
|
|
|
|
<!-- 样本进度条 (复刻 Step2_12) -->
|
|
<div id="buffer-ui">
|
|
<div class="buffer-track">
|
|
<div class="buffer-fill" id="buffer-fill"></div>
|
|
</div>
|
|
<div class="buffer-text" id="buffer-text">0 / 5 FRAGMENTS</div>
|
|
</div>
|
|
|
|
<!-- 占位符 -->
|
|
<div id="analysis-placeholder" style="flex:1; border:1px dashed #333; display:flex; align-items:center; justify-content:center; color:#333; font-size:10px; min-height: 100px; margin-top: 10px;">
|
|
AWAITING DATA
|
|
</div>
|
|
|
|
<!-- 分析与调制界面 -->
|
|
<div id="analysis-wrapper" style="margin-top: 10px;">
|
|
<div id="analysis-container">
|
|
<canvas id="mapCanvas" width="338" height="200"></canvas>
|
|
<div class="axis-txt" style="top:5px; left:50%; transform:translateX(-50%);">HIGH AROUSAL</div>
|
|
<div class="axis-txt" style="bottom:5px; left:50%; transform:translateX(-50%);">LOW AROUSAL</div>
|
|
<div class="axis-txt" style="top:50%; left:5px; transform:translateY(-50%);">NEG</div>
|
|
<div class="axis-txt" style="top:50%; right:5px; transform:translateY(-50%);">POS</div>
|
|
</div>
|
|
|
|
<!-- 药剂控制器 -->
|
|
<div class="label" style="margin-top:10px; text-align:center; color:#888;">REAGENT INJECTION</div>
|
|
<div id="reagent-controls">
|
|
<button class="reagent-btn" id="btn-up"><div class="chem-icon"><div class="chem-fill"></div></div>STIMULANT</button>
|
|
<button class="reagent-btn" id="btn-down"><div class="chem-icon"><div class="chem-fill"></div></div>SEDATIVE</button>
|
|
<button class="reagent-btn" id="btn-left"><div class="chem-icon"><div class="chem-fill"></div></div>AGITATOR</button>
|
|
<button class="reagent-btn" id="btn-right"><div class="chem-icon"><div class="chem-fill"></div></div>EUPHORIC</button>
|
|
</div>
|
|
|
|
<button id="btn-inject">INJECT REAGENT</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 控制台 -->
|
|
<div id="control-panel">
|
|
<div class="slider-box">
|
|
<div class="slider-head"><span>FREQUENCY</span> <span id="val-0">0</span></div>
|
|
<input type="range" id="s-0" min="0" max="100" value="0">
|
|
</div>
|
|
<div class="slider-box">
|
|
<div class="slider-head"><span>NOISE_LVL</span> <span id="val-1">0</span></div>
|
|
<input type="range" id="s-1" min="0" max="100" value="0">
|
|
</div>
|
|
<div class="slider-box">
|
|
<div class="slider-head"><span>AMPLITUDE</span> <span id="val-2">0</span></div>
|
|
<input type="range" id="s-2" min="0" max="100" value="0">
|
|
</div>
|
|
|
|
<button id="btn-main">INITIATE</button>
|
|
</div>
|
|
|
|
<script>
|
|
/* -----------------------------------------------------------
|
|
系统配置
|
|
----------------------------------------------------------- */
|
|
const TARGETS = { 0: 30, 1: 60, 2: 85 };
|
|
const TOLERANCE = 5;
|
|
|
|
let state = {
|
|
p: [0, 0, 0], // Freq, Noise, Amp
|
|
matches: [false, false, false],
|
|
ready: false, // 波形调准
|
|
active: false, // 正在采样
|
|
phase: 0, // 0:Tuning, 1:Collecting, 2:Modulating, 3:Purifying, 4:Done
|
|
collected: 0,
|
|
needed: 5,
|
|
|
|
// 2D Map State
|
|
targetPos: { x: -60, y: -50 }, // Initial offset (High Arousal, Negative)
|
|
targetLocked: false
|
|
};
|
|
|
|
// Canvas Setup
|
|
const cMain = document.getElementById('mainCanvas');
|
|
const ctx = cMain.getContext('2d');
|
|
const cWave = document.getElementById('waveCanvas');
|
|
const wCtx = cWave.getContext('2d');
|
|
const cMap = document.getElementById('mapCanvas');
|
|
const mCtx = cMap.getContext('2d');
|
|
|
|
let w, h, ww, wh;
|
|
|
|
function resize() {
|
|
const tank = document.getElementById('tank-area');
|
|
w = cMain.width = tank.offsetWidth;
|
|
h = cMain.height = tank.offsetHeight;
|
|
ww = cWave.width = document.getElementById('wave-monitor').offsetWidth;
|
|
wh = cWave.height = document.getElementById('wave-monitor').offsetHeight;
|
|
initDust();
|
|
}
|
|
window.addEventListener('resize', resize);
|
|
setTimeout(resize, 100);
|
|
|
|
|
|
/* -----------------------------------------------------------
|
|
1. 视觉:碎片与背景
|
|
----------------------------------------------------------- */
|
|
let dusts = [];
|
|
let fragments = [];
|
|
let particles = []; // 净化粒子
|
|
|
|
class Dust {
|
|
constructor() { this.reset(); }
|
|
reset() {
|
|
this.x = Math.random() * w;
|
|
this.y = Math.random() * h;
|
|
this.z = Math.random() * 2 + 0.5;
|
|
this.vx = (Math.random() - 0.5) * 0.2;
|
|
this.vy = (Math.random() - 0.5) * 0.2;
|
|
this.blink = Math.random() * 100;
|
|
}
|
|
update() {
|
|
this.x += this.vx; this.y += this.vy;
|
|
this.blink += 0.05;
|
|
if(this.x < 0 || this.x > w || this.y < 0 || this.y > h) this.reset();
|
|
}
|
|
draw() {
|
|
const a = (Math.sin(this.blink) + 1) * 0.15 + 0.05;
|
|
ctx.fillStyle = `rgba(0, 255, 204, ${a})`;
|
|
ctx.fillRect(this.x, this.y, this.z, this.z);
|
|
}
|
|
}
|
|
|
|
function initDust() {
|
|
dusts = [];
|
|
for(let i=0; i<300; i++) dusts.push(new Dust());
|
|
}
|
|
|
|
class GlitchFragment {
|
|
constructor() {
|
|
this.x = w/2 + (Math.random()-0.5)*200;
|
|
this.y = h/2 + (Math.random()-0.5)*200;
|
|
this.life = 0;
|
|
this.dead = false;
|
|
this.sucking = false;
|
|
this.purified = false; // 是否被净化
|
|
this.alpha = 1.0;
|
|
|
|
// 形状
|
|
this.blocks = [];
|
|
const count = 4 + Math.random()*4;
|
|
for(let i=0; i<count; i++) {
|
|
this.blocks.push({
|
|
dx: (Math.random()-0.5)*20, dy: (Math.random()-0.5)*20,
|
|
w: 5+Math.random()*10, h: 5+Math.random()*10
|
|
});
|
|
}
|
|
}
|
|
update() {
|
|
// 吸取动画
|
|
if(this.sucking && !this.purified) {
|
|
this.blocks.forEach(b => { b.w *= 0.8; b.h *= 0.8; });
|
|
if(this.blocks[0].w < 0.5) this.dead = true;
|
|
return;
|
|
}
|
|
// 净化动画
|
|
if(this.purified) {
|
|
this.alpha -= 0.02;
|
|
this.blocks.forEach(b => {
|
|
b.dx += (Math.random()-0.5)*2;
|
|
b.dy += (Math.random()-0.5)*2;
|
|
});
|
|
if(this.alpha <= 0) this.dead = true;
|
|
return;
|
|
}
|
|
|
|
this.life++;
|
|
this.x += Math.sin(this.life*0.02)*0.5;
|
|
this.y += Math.cos(this.life*0.03)*0.5;
|
|
}
|
|
draw() {
|
|
if(this.dead) return;
|
|
ctx.save();
|
|
ctx.translate(this.x, this.y);
|
|
ctx.globalAlpha = this.alpha;
|
|
|
|
let color = '#ff3333';
|
|
let shadow = '#ff0000';
|
|
|
|
// 净化变色
|
|
if(this.purified) {
|
|
color = '#ffffff';
|
|
shadow = '#00ffcc';
|
|
} else if (Math.random() > 0.9) {
|
|
color = '#fff'; // Glitch flash
|
|
}
|
|
|
|
ctx.fillStyle = color;
|
|
ctx.shadowBlur = 15; ctx.shadowColor = shadow;
|
|
|
|
this.blocks.forEach(b => {
|
|
const jx = (Math.random()-0.5)*2;
|
|
const jy = (Math.random()-0.5)*2;
|
|
ctx.fillRect(b.dx + jx, b.dy + jy, b.w, b.h);
|
|
});
|
|
ctx.restore();
|
|
}
|
|
hit(mx, my) {
|
|
const dist = Math.hypot(mx - this.x, my - this.y);
|
|
return dist < 30;
|
|
}
|
|
}
|
|
|
|
class Antibody {
|
|
constructor(tx, ty) {
|
|
this.x = Math.random() * w;
|
|
this.y = h + 10; // 从底部升起
|
|
this.tx = tx; this.ty = ty; // 目标碎片位置
|
|
this.speed = 5 + Math.random()*5;
|
|
this.dead = false;
|
|
}
|
|
update() {
|
|
const dx = this.tx - this.x;
|
|
const dy = this.ty - this.y;
|
|
const dist = Math.hypot(dx, dy);
|
|
|
|
this.x += (dx/dist) * this.speed;
|
|
this.y += (dy/dist) * this.speed;
|
|
|
|
if(dist < 10) this.dead = true; // 击中
|
|
}
|
|
draw() {
|
|
ctx.fillStyle = '#00ffcc';
|
|
ctx.beginPath(); ctx.arc(this.x, this.y, 2, 0, Math.PI*2); ctx.fill();
|
|
}
|
|
}
|
|
|
|
/* -----------------------------------------------------------
|
|
2. 示波器逻辑 (使用 Step2_12 算法)
|
|
----------------------------------------------------------- */
|
|
let waveTime = 0;
|
|
function drawWave() {
|
|
// 清除
|
|
wCtx.clearRect(0, 0, ww, wh);
|
|
|
|
const vFreq = state.p[0] / 100;
|
|
const vNoise = state.p[1] / 100;
|
|
const vAmp = state.p[2] / 100;
|
|
const cy = wh / 2;
|
|
|
|
// 样式
|
|
const color = state.ready ? '#00ffcc' : '#006655';
|
|
wCtx.strokeStyle = color;
|
|
wCtx.lineWidth = state.ready ? 2 : 1;
|
|
wCtx.shadowBlur = state.ready ? 10 : 0;
|
|
wCtx.shadowColor = '#00ffcc';
|
|
|
|
wCtx.beginPath();
|
|
|
|
let penDown = true;
|
|
|
|
for(let x = 0; x < ww; x += 2) {
|
|
// 1. Freq 影响基础波形
|
|
const baseFreq = 0.02 + vFreq * 0.1;
|
|
let yRaw = Math.sin(x * baseFreq + waveTime * (2 + vFreq * 5));
|
|
|
|
// 添加谐波
|
|
if(vFreq > 0.3) yRaw += Math.sin(x * baseFreq * 2 + waveTime) * 0.3;
|
|
if(vFreq > 0.6) yRaw += Math.cos(x * baseFreq * 5 - waveTime) * 0.2;
|
|
|
|
// 方波削顶
|
|
if (vFreq > 0.8) yRaw = Math.sign(yRaw) * 1.1;
|
|
|
|
// 2. Amp 影响振幅 + 脉冲
|
|
let amp = 10 + vAmp * 30;
|
|
if (vAmp > 0.2) {
|
|
const pulseSpeed = 2 + vAmp * 5;
|
|
const envelope = 0.8 + Math.sin(waveTime * pulseSpeed) * 0.4;
|
|
amp *= envelope;
|
|
}
|
|
|
|
// 3. Noise 影响
|
|
let noise = 0;
|
|
if (vNoise > 0.1) {
|
|
noise = (Math.random() - 0.5) * vNoise * 30;
|
|
if (Math.random() < 0.01 * vNoise) noise += (Math.random() - 0.5) * 100;
|
|
}
|
|
|
|
const y = cy + (yRaw * amp) + noise;
|
|
|
|
// 断线处理
|
|
if (Math.abs(y - cy) > wh/2) penDown = false;
|
|
|
|
if (x === 0 || !penDown) wCtx.moveTo(x, y);
|
|
else wCtx.lineTo(x, y);
|
|
|
|
penDown = true;
|
|
}
|
|
wCtx.stroke();
|
|
|
|
waveTime += 0.05;
|
|
}
|
|
|
|
/* -----------------------------------------------------------
|
|
3. 核心状态控制
|
|
----------------------------------------------------------- */
|
|
function updateParam(idx, val) {
|
|
val = parseInt(val);
|
|
state.p[idx] = val;
|
|
document.getElementById(`val-${idx}`).innerText = val;
|
|
document.getElementById(`txt-${idx}`).innerText = val.toString().padStart(2,'0');
|
|
|
|
state.matches[idx] = Math.abs(val - TARGETS[idx]) <= TOLERANCE;
|
|
const el = document.getElementById(`row-${idx}`);
|
|
if(state.matches[idx]) el.classList.add('matched');
|
|
else el.classList.remove('matched');
|
|
|
|
checkTotalReady();
|
|
}
|
|
|
|
function checkTotalReady() {
|
|
const all = state.matches.every(x=>x);
|
|
state.ready = all;
|
|
const btn = document.getElementById('btn-main');
|
|
const status = document.getElementById('sys-status');
|
|
const monitor = document.getElementById('wave-monitor');
|
|
|
|
if(all) {
|
|
btn.classList.add('ready');
|
|
monitor.classList.add('locked'); // 波形背景发光
|
|
|
|
if(state.phase === 0) {
|
|
status.innerText = "RESONANCE STABLE - READY";
|
|
status.style.color = "#00ffcc";
|
|
}
|
|
} else {
|
|
btn.classList.remove('ready');
|
|
monitor.classList.remove('locked');
|
|
|
|
if(state.phase === 0) {
|
|
status.innerText = "TUNING REQUIRED...";
|
|
status.style.color = "#666";
|
|
}
|
|
}
|
|
}
|
|
|
|
// 主按钮逻辑
|
|
document.getElementById('btn-main').onclick = () => {
|
|
if(state.phase === 0) { // TUNING -> COLLECTING
|
|
if(!state.ready) return;
|
|
startCollection();
|
|
} else if (state.phase === 1) { // COLLECTING -> ABORT
|
|
resetSystem();
|
|
}
|
|
};
|
|
|
|
function startCollection() {
|
|
state.phase = 1;
|
|
state.active = true;
|
|
state.collected = 0;
|
|
fragments = []; // 重置旧的
|
|
|
|
const btn = document.getElementById('btn-main');
|
|
btn.classList.add('active');
|
|
btn.innerText = "ABORT";
|
|
|
|
document.getElementById('tank-area').classList.add('sampling');
|
|
document.getElementById('sys-status').innerText = "COLLECTING EMOTION SAMPLES...";
|
|
document.getElementById('sys-status').style.color = "#ff3333";
|
|
|
|
document.getElementById('analysis-wrapper').style.display = 'none';
|
|
document.getElementById('analysis-placeholder').style.display = 'flex';
|
|
|
|
updateSampleUI();
|
|
}
|
|
|
|
function resetSystem() {
|
|
state.phase = 0;
|
|
state.active = false;
|
|
state.targetPos = { x: -60, y: -50 }; // Reset target
|
|
state.targetLocked = false;
|
|
|
|
const btn = document.getElementById('btn-main');
|
|
btn.classList.remove('active');
|
|
btn.innerText = "INITIATE";
|
|
|
|
document.getElementById('tank-area').classList.remove('sampling');
|
|
document.getElementById('tank-area').classList.remove('purified');
|
|
document.getElementById('sys-status').innerText = "TUNING REQUIRED...";
|
|
document.getElementById('sys-status').style.color = "#666";
|
|
|
|
fragments = [];
|
|
particles = [];
|
|
|
|
document.getElementById('analysis-wrapper').style.display = 'none';
|
|
document.getElementById('analysis-placeholder').style.display = 'flex';
|
|
document.getElementById('btn-inject').style.display = 'none';
|
|
|
|
checkTotalReady(); // Re-check status text
|
|
}
|
|
|
|
// 收集碎片逻辑
|
|
function spawnLoop() {
|
|
// 只在收集阶段生成,且数量有限
|
|
if(state.phase === 1 && state.collected < state.needed && fragments.length < 8) {
|
|
if(Math.random() > 0.97) fragments.push(new GlitchFragment());
|
|
}
|
|
requestAnimationFrame(spawnLoop);
|
|
}
|
|
spawnLoop();
|
|
|
|
cMain.addEventListener('mousedown', e => {
|
|
if(state.phase !== 1) return; // 只有收集阶段可以点击
|
|
|
|
const r = cMain.getBoundingClientRect();
|
|
const mx = e.clientX - r.left;
|
|
const my = e.clientY - r.top;
|
|
|
|
for(let f of fragments) {
|
|
if(!f.sucking && !f.dead && f.hit(mx, my)) {
|
|
f.sucking = true;
|
|
state.collected++;
|
|
updateSampleUI();
|
|
spawnFloatText(e.clientX, e.clientY, "SAMPLE +1");
|
|
|
|
if(state.collected >= state.needed) {
|
|
finishCollection();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
function updateSampleUI() {
|
|
const pct = (state.collected / state.needed) * 100;
|
|
document.getElementById('buffer-fill').style.width = pct + '%';
|
|
document.getElementById('buffer-text').innerText = `${state.collected} / ${state.needed} FRAGMENTS`;
|
|
}
|
|
|
|
function spawnFloatText(x, y, text) {
|
|
const el = document.createElement('div');
|
|
el.className = 'pop-text';
|
|
el.innerText = text;
|
|
el.style.left = x + 'px';
|
|
el.style.top = y + 'px';
|
|
document.body.appendChild(el);
|
|
setTimeout(()=>el.remove(), 1000);
|
|
}
|
|
|
|
function finishCollection() {
|
|
state.phase = 2; // Analysis / Modulation
|
|
state.active = false; // 停止生成和点击
|
|
document.getElementById('tank-area').classList.remove('sampling');
|
|
|
|
document.getElementById('sys-status').innerText = "ANALYSIS COMPLETE. MODULATION REQUIRED.";
|
|
document.getElementById('sys-status').style.color = "#00ffcc";
|
|
|
|
document.getElementById('btn-main').classList.remove('active');
|
|
document.getElementById('btn-main').innerText = "LOCKED"; // 锁定主按钮
|
|
document.getElementById('btn-main').style.cursor = "default";
|
|
|
|
// Show Analysis
|
|
document.getElementById('analysis-placeholder').style.display = 'none';
|
|
document.getElementById('analysis-wrapper').style.display = 'flex';
|
|
|
|
drawAnalysisLoop();
|
|
}
|
|
|
|
/* -----------------------------------------------------------
|
|
4. 分析与调制 (Modulation)
|
|
----------------------------------------------------------- */
|
|
// 药剂按钮绑定
|
|
function moveTarget(dx, dy) {
|
|
if(state.phase !== 2 || state.targetLocked) return;
|
|
state.targetPos.x += dx;
|
|
state.targetPos.y += dy;
|
|
checkModulation();
|
|
}
|
|
|
|
document.getElementById('btn-up').onclick = () => moveTarget(0, -5);
|
|
document.getElementById('btn-down').onclick = () => moveTarget(0, 5);
|
|
document.getElementById('btn-left').onclick = () => moveTarget(-5, 0);
|
|
document.getElementById('btn-right').onclick = () => moveTarget(5, 0);
|
|
|
|
function checkModulation() {
|
|
const dist = Math.hypot(state.targetPos.x, state.targetPos.y);
|
|
const btn = document.getElementById('btn-inject');
|
|
|
|
if(dist < 5) {
|
|
state.targetLocked = true;
|
|
state.targetPos.x = 0;
|
|
state.targetPos.y = 0;
|
|
|
|
btn.style.display = 'block';
|
|
setTimeout(() => btn.classList.add('ready'), 100);
|
|
|
|
document.getElementById('sys-status').innerText = "COMPOUND BALANCED. READY TO INJECT.";
|
|
}
|
|
}
|
|
|
|
// 注入药剂
|
|
document.getElementById('btn-inject').onclick = () => {
|
|
if(!state.targetLocked) return;
|
|
startPurification();
|
|
};
|
|
|
|
function startPurification() {
|
|
state.phase = 3;
|
|
document.getElementById('btn-inject').classList.remove('ready');
|
|
document.getElementById('btn-inject').innerText = "INJECTING...";
|
|
document.getElementById('tank-area').classList.add('purified'); // 背景变色
|
|
document.getElementById('sys-status').innerText = "NEUTRALIZING AGENT RELEASED...";
|
|
|
|
// 发射抗体
|
|
fragments.forEach(f => {
|
|
// 为每个碎片生成几个抗体粒子
|
|
for(let i=0; i<3; i++) {
|
|
particles.push(new Antibody(f.x, f.y));
|
|
}
|
|
// 稍微延迟后碎片开始净化
|
|
setTimeout(() => { f.purified = true; }, 1000 + Math.random()*1000);
|
|
});
|
|
}
|
|
|
|
function drawAnalysisLoop() {
|
|
if(state.phase < 2) return; // Stop if reset
|
|
|
|
const cw = cMap.width;
|
|
const ch = cMap.height;
|
|
const cx = cw/2;
|
|
const cy = ch/2;
|
|
|
|
mCtx.clearRect(0,0,cw,ch);
|
|
|
|
// Grid
|
|
mCtx.strokeStyle = '#333';
|
|
mCtx.lineWidth = 1;
|
|
mCtx.beginPath();
|
|
mCtx.moveTo(cx, 0); mCtx.lineTo(cx, ch);
|
|
mCtx.moveTo(0, cy); mCtx.lineTo(cw, cy);
|
|
mCtx.stroke();
|
|
mCtx.strokeStyle = '#222';
|
|
mCtx.beginPath();
|
|
mCtx.arc(cx, cy, 30, 0, Math.PI*2);
|
|
mCtx.arc(cx, cy, 60, 0, Math.PI*2);
|
|
mCtx.stroke();
|
|
|
|
// Target Point
|
|
const tx = cx + state.targetPos.x;
|
|
const ty = cy + state.targetPos.y;
|
|
|
|
// Dashed Line to Center
|
|
mCtx.strokeStyle = '#555';
|
|
mCtx.setLineDash([4,4]);
|
|
mCtx.beginPath();
|
|
mCtx.moveTo(cx, cy);
|
|
mCtx.lineTo(tx, ty);
|
|
mCtx.stroke();
|
|
mCtx.setLineDash([]);
|
|
|
|
// Draw Point
|
|
mCtx.fillStyle = state.targetLocked ? '#fff' : '#ff3333';
|
|
mCtx.shadowBlur = 10;
|
|
mCtx.shadowColor = state.targetLocked ? '#00ffcc' : '#ff0000';
|
|
mCtx.beginPath();
|
|
mCtx.arc(tx, ty, 6, 0, Math.PI*2);
|
|
mCtx.fill();
|
|
mCtx.shadowBlur = 0;
|
|
|
|
requestAnimationFrame(drawAnalysisLoop);
|
|
}
|
|
|
|
/* -----------------------------------------------------------
|
|
5. 主循环
|
|
----------------------------------------------------------- */
|
|
function loop() {
|
|
requestAnimationFrame(loop);
|
|
ctx.clearRect(0,0,w,h);
|
|
|
|
dusts.forEach(d => { d.update(); d.draw(); });
|
|
|
|
// 净化阶段检测
|
|
if(state.phase === 3) {
|
|
// 处理抗体
|
|
particles.forEach((p,i) => {
|
|
p.update();
|
|
p.draw();
|
|
if(p.dead) particles.splice(i, 1);
|
|
});
|
|
|
|
// 检查是否全部清除
|
|
if(fragments.length === 0 && particles.length === 0) {
|
|
state.phase = 4;
|
|
document.getElementById('sys-status').innerText = "PURIFICATION COMPLETE. SYSTEM CLEAN.";
|
|
document.getElementById('sys-status').style.color = "#fff";
|
|
document.getElementById('btn-inject').innerText = "DONE";
|
|
}
|
|
}
|
|
|
|
// 绘制连线 (只在未净化时)
|
|
if(state.phase < 3 && fragments.length > 1) {
|
|
ctx.strokeStyle = 'rgba(255, 50, 50, 0.3)';
|
|
ctx.beginPath();
|
|
fragments.forEach((f1, i) => {
|
|
fragments.forEach((f2, j) => {
|
|
if(i<j && !f1.dead && !f2.dead) {
|
|
const d = Math.hypot(f1.x-f2.x, f1.y-f2.y);
|
|
if(d < 150) {
|
|
ctx.moveTo(f1.x, f1.y);
|
|
ctx.lineTo(f2.x, f2.y);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 碎片
|
|
fragments.forEach((f, i) => {
|
|
f.update();
|
|
f.draw();
|
|
if(f.dead) fragments.splice(i,1);
|
|
});
|
|
|
|
drawWave();
|
|
}
|
|
loop();
|
|
|
|
// Init
|
|
[0,1,2].forEach(i => document.getElementById(`s-${i}`).oninput = e => updateParam(i, e.target.value));
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
|
|
|