-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpage3-state-machines.html
More file actions
203 lines (190 loc) · 24.1 KB
/
Copy pathpage3-state-machines.html
File metadata and controls
203 lines (190 loc) · 24.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page 3: State Machines ↔ Refractory | Neuro-to-Digital</title>
<style>
:root{--bg:#0a0a0f;--surface:#14141f;--border:#2a2a3a;--text:#c8c8d4;--text-dim:#7a7a8a;--accent:#4ec9b0;--accent-glow:rgba(78,201,176,0.3);--danger:#f4476b;--warn:#e5c07b;--purple:#c678dd;--font:'SF Mono','Fira Code','Consolas',monospace;--radius:10px}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;display:flex;flex-direction:column;align-items:center}
.top-bar{width:100%;max-width:1200px;display:flex;justify-content:space-between;align-items:center;padding:16px 32px 0}
.top-bar nav{display:flex;gap:14px;align-items:center}
.top-bar nav a{color:var(--text-dim);text-decoration:none;font-size:11px;transition:color .2s}
.top-bar nav a:hover{color:var(--accent)}
.lang-toggle{display:flex;gap:2px;border:1px solid var(--border);border-radius:4px;overflow:hidden}
.lang-toggle button{padding:4px 10px;font-size:11px;font-family:var(--font);border:none;background:transparent;color:var(--text-dim);cursor:pointer;transition:all .2s}
.lang-toggle button.active{background:rgba(78,201,176,0.15);color:var(--accent)}
header{width:100%;max-width:1200px;padding:12px 32px 0;display:flex;align-items:baseline;gap:12px}
header .logo{font-size:13px;color:var(--accent);letter-spacing:2px;text-transform:uppercase}
header h1{font-size:20px;font-weight:600;color:#e8e8f0}
.intro-bar{width:100%;max-width:1200px;padding:12px 32px 0}
.intro-inner{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px;padding:14px 18px;font-size:12px;line-height:1.7;color:var(--text-dim)}
main{width:100%;max-width:1200px;display:grid;grid-template-columns:1fr 1fr;gap:20px;padding:16px 32px;flex:1}
@media(max-width:860px){main{grid-template-columns:1fr}}
.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;display:flex;flex-direction:column;gap:16px}
.panel h2{font-size:14px;font-weight:600;color:#e0e0e8;text-transform:uppercase;letter-spacing:1px;display:flex;align-items:center;gap:8px}
.panel h2 .dot{width:8px;height:8px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent-glow)}
.ff-selector{display:flex;gap:6px}
.ff-btn{flex:1;padding:9px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text-dim);font-family:var(--font);font-size:11px;cursor:pointer;transition:all .2s;text-align:center}
.ff-btn.active{border-color:var(--accent);background:rgba(78,201,176,0.1);color:var(--accent)}
.sr-controls{display:flex;gap:14px;justify-content:center}
.sr-btn{padding:12px 20px;border:2px solid var(--border);border-radius:8px;background:transparent;color:var(--text);font-family:var(--font);font-size:13px;cursor:pointer;transition:all .2s;font-weight:600}
.sr-btn.set:hover{border-color:var(--accent);background:rgba(78,201,176,0.1);color:var(--accent)}
.sr-btn.reset:hover{border-color:var(--danger);background:rgba(244,71,107,0.1);color:var(--danger)}
.sr-btn.set:active,.sr-btn.reset:active{transform:scale(0.96)}
.state-display{display:flex;align-items:center;gap:20px;justify-content:center}
.state-led{width:56px;height:56px;border-radius:50%;border:3px solid var(--border);display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;transition:all .3s}
.state-led.on{border-color:var(--accent);background:rgba(78,201,176,0.2);color:var(--accent);box-shadow:0 0 30px var(--accent-glow)}
.state-led.off{border-color:var(--text-dim);background:rgba(100,100,120,0.1);color:var(--text-dim)}
.clock-btn{padding:10px 20px;border:2px solid var(--warn);border-radius:8px;background:transparent;color:var(--warn);font-family:var(--font);font-size:12px;cursor:pointer;transition:all .2s}
.clock-btn:hover{background:rgba(229,192,123,0.1)}
.state-table{width:100%;border-collapse:collapse;font-size:11px}
.state-table th,.state-table td{padding:7px 10px;text-align:center;border:1px solid var(--border)}
.state-table th{color:var(--text-dim);font-size:9px;text-transform:uppercase}
.state-table tr.active-row{background:rgba(78,201,176,0.08)}
.state-table tr.active-row td{color:var(--accent)}
.neuron-canvas-wrap{display:flex;justify-content:center}
canvas{border-radius:8px;background:#080810;max-width:100%}
.neuron-legend{display:flex;flex-wrap:wrap;gap:12px;font-size:10px;color:var(--text-dim)}
.neuron-legend span{display:flex;align-items:center;gap:4px}
.ld{width:9px;height:9px;border-radius:50%;display:inline-block}
.ld.abs{background:var(--danger)}.ld.rel{background:var(--warn)}.ld.ltp{background:var(--purple)}.ld.ap{background:var(--accent)}
.bottom-sections{width:100%;max-width:1200px;padding:0 32px 20px;display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media(max-width:860px){.bottom-sections{grid-template-columns:1fr}}
.bottom-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px;font-size:12px;line-height:1.7;color:var(--text-dim)}
.bottom-box strong{color:var(--accent)}.bottom-box h3{font-size:13px;color:#ddd;margin-bottom:6px}
.bottom-box.breaks{border-left:3px solid var(--warn)}.bottom-box.breaks h3{color:var(--warn)}
footer{width:100%;max-width:1200px;padding:0 32px 16px;font-size:10px;color:var(--text-dim);display:flex;justify-content:space-between}
footer a{color:var(--text-dim);text-decoration:none;transition:color .2s}footer a:hover{color:var(--accent)}
</style>
</head>
<body>
<div class="top-bar">
<nav><a href="index.html">← 首页</a><a href="knowledge-tree.html">🌳 知识树</a><a href="page4-combinational.html">下一页 →</a></nav>
<div class="lang-toggle"><button id="btn-zh" class="active">中</button><button id="btn-en">EN</button></div>
</div>
<header><span class="logo">Neuro→Digital</span><span style="color:var(--text-dim);margin:0 8px;font-size:14px">/</span><h1 id="page-title">Page 3: State Machines ↔ Refractory</h1></header>
<div class="intro-bar"><div class="intro-inner" id="intro-text"></div></div>
<main>
<section class="panel">
<h2><span class="dot"></span> <span id="left-title">Sequential Logic — Flip-Flops</span></h2>
<div class="ff-selector" id="ff-sel">
<button class="ff-btn active" data-ff="SR">SR Latch</button>
<button class="ff-btn" data-ff="D">D Flip-Flop</button>
<button class="ff-btn" data-ff="JK">JK Flip-Flop</button>
</div>
<div id="ff-diagram" style="display:flex;flex-direction:column;align-items:center;gap:14px;padding:8px 0"></div>
<table class="state-table" id="state-table"><thead></thead><tbody></tbody></table>
</section>
<section class="panel">
<h2><span class="dot"></span> <span id="right-title">Neuron — Refractory & Plasticity</span></h2>
<div class="neuron-canvas-wrap"><canvas id="ncanvas" width="440" height="460"></canvas></div>
<div class="neuron-legend" id="nlegend"></div>
</section>
</main>
<div class="bottom-sections">
<div class="bottom-box"><h3 id="explain-title"></h3><div id="explain-text"></div></div>
<div class="bottom-box breaks"><h3 id="breaks-title"></h3><div id="breaks-text"></div></div>
</div>
<footer><span>Neuro-to-Digital · Summer 2026</span><span>Page 3/6 · <a href="page4-combinational.html">Next →</a></span></footer>
<script>
const I18={
zh:{
pageTitle:'Page 3: 状态机 — 不应期与记忆痕迹',
intro:'💡 <strong>这是什么?</strong> 逻辑门是"即时的"——输入变→输出立刻变。但计算机需要"记住"东西。Flip-Flop = 1 bit 的记忆。SR 锁存器是全部计算机内存的祖先。D Flip-Flop 加上时钟线,就是 CPU 寄存器的基本单元。JK FF 能翻转,计数器和分频器的基础。',
leftTitle:'时序逻辑 — Flip-Flop',rightTitle:'神经元 — 不应期与可塑性',
nlegend:'<span><span class="ld abs"></span>绝对不应期</span><span><span class="ld rel"></span>相对不应期</span><span><span class="ld ap"></span>动作电位</span><span><span class="ld ltp"></span>LTP 记忆痕迹</span>',
explainTitle:'神经类比解释',
explain:{SR:'<strong>SR 锁存器 → 不应期作为状态记忆。</strong>发放后,神经元进入绝对不应期(~1-2ms,Na⁺ 通道失活)——<em>无论如何都不能再发</em>,就像 Reset 后的 Q=0。相对不应期(~3-5ms,K⁺ 通道仍开放)——<em>能发但需要更强输入</em>,就像 SR 的亚稳态过渡区。神经元的近期历史决定其当前响应能力——就像锁存器的输出取决于之前的状态。',D:'<strong>D Flip-Flop → STDP(脉冲时间依赖可塑性)。</strong>突触前 spike 恰好比突触后 spike 早一点到达 → 突触强化(LTP)。<em>谁先到?</em> 这个先后关系决定了结果——就像时钟边沿在精确时刻捕获 D 的值。时机决定一切。',JK:'<strong>JK Flip-Flop → 抑制后反弹(Post-inhibitory Rebound)。</strong>一些神经元在强抑制解除后会<em>反弹发放</em>——就像 J=K=1 时 Q 翻转。丘脑皮层回路利用这个机制产生睡眠纺锤波——在去极化和超极化状态之间节律性地翻转,就像一个生物 JK 振荡器。'},
breaksTitle:'⚠️ 类比哪里会破裂',
breaks:{SR:'不应期的持续时间是连续量(电压和时间依赖),不像 SR 锁存器的离散 Set/Reset。两个突触输入的精确时间差(而非"谁按了")决定结果。而且神经元的"状态"分布在多个离子通道动力学中,不是一个 bit。',D:'STDP 的时间窗口是几十毫秒(不是精确的时钟边沿),而且窗口形状不对称——LTP 窗口比 LTD 窗口窄。不像 D FF 的严格边沿触发。突触强度的变化是渐进的(每次 ±1-5%),不像 D FF 的离散翻转。',JK:'JK 的翻转是纯数字的 0↔1,而神经元的抑制后反弹高度依赖抑制时长、强度和膜电位初始状态。不是每次 J=K=1 都会翻转——"有时翻有时不翻"更接近真实神经元的行为。'},
},
en:{
pageTitle:'Page 3: State Machines — Refractory Period & Memory',
intro:'💡 <strong>What is this?</strong> Logic gates are "instantaneous" — input changes, output changes immediately. But computers need to "remember" things. A Flip-Flop = 1 bit of memory. The SR latch is the ancestor of all computer memory. The D Flip-Flop adds a clock — it\'s the basic unit of every CPU register. The JK toggles — the basis of counters and frequency dividers.',
leftTitle:'Sequential Logic — Flip-Flops',rightTitle:'Neuron — Refractory & Plasticity',
nlegend:'<span><span class="ld abs"></span>Absolute Refractory</span><span><span class="ld rel"></span>Relative Refractory</span><span><span class="ld ap"></span>Action Potential</span><span><span class="ld ltp"></span>LTP Memory Trace</span>',
explainTitle:'Neural Analogy Explained',
explain:{SR:'<strong>SR Latch → Refractory Period as State Memory.</strong> After firing, a neuron enters absolute refractory (~1-2ms, Na⁺ channels inactivated) — it <em>cannot</em> fire no matter the input, like Q=0 after Reset. Relative refractory (~3-5ms, K⁺ channels still open) — <em>can</em> fire but needs stronger input, like the SR metastable transition. The neuron\'s recent history determines its current responsiveness — just as the latch output depends on its prior state.',D:'<strong>D Flip-Flop → STDP (Spike-Timing-Dependent Plasticity).</strong> When a presynaptic spike consistently arrives just before a postsynaptic spike, the synapse strengthens (LTP). <em>Which came first?</em> The timing relationship determines the outcome — like the clock edge capturing D at exactly the right moment. Phase is everything.',JK:'<strong>JK Flip-Flop → Post-Inhibitory Rebound.</strong> Some neurons <em>rebound-fire</em> after strong inhibition is released — like J=K=1 toggling Q. Thalamocortical circuits use this for sleep spindles — rhythmic toggling between depolarized and hyperpolarized states, like a biological JK oscillator.'},
breaksTitle:'⚠️ Where the Analogy Breaks',
breaks:{SR:'Refractory duration is a continuous quantity (voltage- and time-dependent), not discrete Set/Reset. The precise time difference between two synaptic inputs (rather than "which was pressed") determines the outcome. And the neuron\'s "state" is distributed across multiple ion channel kinetics — not a single bit.',D:'STDP time windows span tens of milliseconds (not a precise clock edge), and the window is asymmetric — the LTP window is narrower than the LTD window. Unlike the strict edge-triggered D FF. Synaptic strength changes are gradual (~1-5% per pair), not discrete flips.',JK:'The JK toggle is purely digital 0↔1, but post-inhibitory rebound strength depends heavily on inhibition duration, intensity, and initial membrane state. J=K=1 doesn\'t always toggle — "sometimes flips, sometimes doesn\'t" is closer to real neuron behavior.'},
}
};
let lang=localStorage.getItem('ntd-lang')||'zh';
function t(p){const k=p.split('.');let v=I18[lang];for(const kk of k){if(v===undefined)return p;v=v[kk];}return v||p;}
let ffType='SR',q=0,dInput=true,sActive=false,rActive=false;
const ffDiagram=document.getElementById('ff-diagram'),stateTable=document.getElementById('state-table');
const canvas=document.getElementById('ncanvas'),ctx=canvas.getContext('2d');
const CW=440,CH=460;
let ns={v:-70,firing:false,absRefractory:0,relRefractory:0,ltpLevel:0,lastSpikeTime:-1000,spikeHistory:[]};
let lastT=0;
function renderFF(){
if(ffType==='SR'){
ffDiagram.innerHTML=`<div style="font-size:11px;color:var(--text-dim)">${lang==='zh'?'点击 Set 或 Reset':'Click Set or Reset'}</div><div class="sr-controls"><button class="sr-btn set" id="btn-set">S = 1</button><button class="sr-btn reset" id="btn-reset">R = 1</button></div><div class="state-display"><span style="color:var(--text-dim);font-size:12px">Q =</span><div class="state-led ${q?'on':'off'}">${q?'1':'0'}</div><span style="color:var(--text-dim);font-size:12px">Q̄ = ${q?'0':'1'}</span></div>`;
setTimeout(()=>{document.getElementById('btn-set')?.addEventListener('mousedown',()=>{sActive=true;update();});document.getElementById('btn-set')?.addEventListener('mouseup',()=>{sActive=false;update();});document.getElementById('btn-reset')?.addEventListener('mousedown',()=>{rActive=true;update();});document.getElementById('btn-reset')?.addEventListener('mouseup',()=>{rActive=false;update();});},50);
const rows=[{S:0,R:0,q:'Q (hold)'},{S:0,R:1,q:0},{S:1,R:0,q:1},{S:1,R:1,q:'X (invalid!)'}];
let ai=-1;if(!sActive&&!rActive)ai=0;else if(!sActive&&rActive)ai=1;else if(sActive&&!rActive)ai=2;else ai=3;
stateTable.querySelector('thead').innerHTML='<tr><th>S</th><th>R</th><th>Q</th><th>Q̄</th></tr>';
stateTable.querySelector('tbody').innerHTML=rows.map((r,i)=>`<tr class="${i===ai?'active-row':''}"><td>${r.S}</td><td>${r.R}</td><td>${r.q}</td><td>${i===0?'Q̄':'1'}</td></tr>`).join('');
}else if(ffType==='D'){
ffDiagram.innerHTML=`<div style="font-size:11px;color:var(--text-dim)">${lang==='zh'?'D FF: 时钟边沿时输出 = D':'D FF: output = D on clock edge'}</div><div style="display:flex;align-items:center;gap:8px;justify-content:center"><span style="color:var(--text-dim);font-size:12px">D =</span><input type="checkbox" id="d-check" ${dInput?'checked':''}><span style="color:var(--accent);font-size:12px">${dInput?'1':'0'}</span></div><div style="text-align:center"><button class="clock-btn" id="btn-clock">⬆ CLK Edge</button></div><div class="state-display"><span style="color:var(--text-dim);font-size:12px">Q =</span><div class="state-led ${q?'on':'off'}">${q?'1':'0'}</div></div>`;
setTimeout(()=>{document.getElementById('d-check')?.addEventListener('change',e=>{dInput=e.target.checked;renderAll();});document.getElementById('btn-clock')?.addEventListener('click',()=>{q=dInput?1:0;renderAll();});},50);
stateTable.querySelector('thead').innerHTML='<tr><th>CLK</th><th>D</th><th>Q(next)</th></tr>';
stateTable.querySelector('tbody').innerHTML=[{clk:'↑',d:0,qn:0},{clk:'↑',d:1,qn:1}].map(r=>`<tr class="${q===r.qn&&dInput===(!!r.d)?'active-row':''}"><td>${r.clk}</td><td>${r.d}</td><td>${r.qn}</td></tr>`).join('');
}else{
ffDiagram.innerHTML=`<div style="font-size:11px;color:var(--text-dim)">${lang==='zh'?'JK: J=Set, K=Reset, J=K=1 → 翻转':'JK: J=Set, K=Reset, J=K=1 → Toggle'}</div><div style="display:flex;gap:10px;justify-content:center"><button class="sr-btn set" id="btn-j">J = 1</button><button class="sr-btn reset" id="btn-k">K = 1</button><button class="clock-btn" id="btn-toggle">J=K=1 (Toggle)</button></div><div class="state-display"><span style="color:var(--text-dim);font-size:12px">Q =</span><div class="state-led ${q?'on':'off'}">${q?'1':'0'}</div></div>`;
setTimeout(()=>{document.getElementById('btn-j')?.addEventListener('click',()=>{q=1;renderAll();});document.getElementById('btn-k')?.addEventListener('click',()=>{q=0;renderAll();});document.getElementById('btn-toggle')?.addEventListener('click',()=>{q=q?0:1;renderAll();});},50);
stateTable.querySelector('thead').innerHTML='<tr><th>J</th><th>K</th><th>Q(next)</th></tr>';
stateTable.querySelector('tbody').innerHTML=[{j:0,k:0,qn:'Q (hold)'},{j:0,k:1,qn:0},{j:1,k:0,qn:1},{j:1,k:1,qn:'Q̄ (toggle)'}].map(r=>`<tr><td>${r.j}</td><td>${r.k}</td><td>${r.qn}</td></tr>`).join('');
}
}
function update(){if(ffType==='SR'){if(sActive&&!rActive)q=1;else if(!sActive&&rActive)q=0;else if(sActive&&rActive)q=-1;}renderAll();}
function drawNeuron(ts){
const dt=lastT?Math.min(ts-lastT,50):16;lastT=ts;ctx.clearRect(0,0,CW,CH);
const stimulated=q===1,inhibited=q===-1;
if(ns.absRefractory>0)ns.absRefractory-=dt;if(ns.relRefractory>0)ns.relRefractory-=dt;
if(!stimulated)ns.ltpLevel=Math.max(0,ns.ltpLevel-dt*0.0003);ns.ltpLevel=Math.min(1,ns.ltpLevel);
const canFire=ns.absRefractory<=0&&stimulated&&!inhibited,thresh=-55-ns.ltpLevel*8,needsStronger=ns.relRefractory>0;
if(canFire){const et=needsStronger?thresh+8:thresh;ns.v+=(et-ns.v)*0.3*(dt/16);if(ns.v>=et){ns.firing=true;ns.v=30;ns.absRefractory=120;ns.relRefractory=240;ns.lastSpikeTime=ts;ns.ltpLevel=Math.min(1,ns.ltpLevel+0.12);ns.spikeHistory.push({time:ts,ltp:ns.ltpLevel});if(ns.spikeHistory.length>20)ns.spikeHistory.shift();}}
else{ns.v+=(-70-ns.v)*0.08*(dt/16);ns.firing=false;}
const somaX=CW/2,somaY=180,somaR=35;
const de=[[somaX-100,somaY-80],[somaX-50,somaY-100],[somaX+50,somaY-100],[somaX+100,somaY-80]];
de.forEach(([dx,dy])=>{ctx.beginPath();ctx.moveTo(dx,dy);ctx.quadraticCurveTo((dx+somaX)/2,(dy+somaY)/2-10,somaX,somaY-somaR*0.4);ctx.strokeStyle='rgba(180,190,200,0.2)';ctx.lineWidth=1.5;ctx.stroke();const active=stimulated&&ns.absRefractory<=0;ctx.beginPath();ctx.arc(dx,dy,5,0,Math.PI*2);ctx.fillStyle=active?'rgba(78,201,176,0.5)':'rgba(78,201,176,0.08)';ctx.fill();ctx.strokeStyle=active?'rgba(78,201,176,0.7)':'rgba(78,201,176,0.2)';ctx.stroke();if(active){ctx.beginPath();ctx.arc(dx,dy,9,0,Math.PI*2);ctx.fillStyle='rgba(78,201,176,0.12)';ctx.fill();}});
const vNorm=(ns.v+70)/100;let gc='78,201,176';if(ns.absRefractory>0)gc='244,71,107';else if(ns.relRefractory>0)gc='229,192,123';
const glowAlpha=Math.min(vNorm,1.3)*0.4,glow=ctx.createRadialGradient(somaX,somaY,somaR*0.5,somaX,somaY,somaR*1.8);glow.addColorStop(0,`rgba(${gc},${glowAlpha})`);glow.addColorStop(1,'rgba(0,0,0,0)');ctx.beginPath();ctx.arc(somaX,somaY,somaR*1.8,0,Math.PI*2);ctx.fillStyle=glow;ctx.fill();
ctx.beginPath();ctx.arc(somaX,somaY,somaR,0,Math.PI*2);const sg=ctx.createRadialGradient(somaX-8,somaY-8,somaR*0.1,somaX,somaY,somaR);sg.addColorStop(0,'#3a5a50');sg.addColorStop(1,'#1a2a24');ctx.fillStyle=sg;ctx.fill();ctx.strokeStyle=`rgba(${gc},0.5)`;ctx.lineWidth=2;ctx.stroke();
const hx=somaX,hy=somaY+somaR+5;ctx.beginPath();ctx.moveTo(hx-14,hy+10);ctx.lineTo(hx+14,hy+10);ctx.lineTo(hx,hy-10);ctx.closePath();const hp2=Math.min(vNorm,1.2);ctx.fillStyle=`rgba(229,192,123,${0.15+hp2*0.5})`;ctx.fill();ctx.strokeStyle=`rgba(229,192,123,${0.3+hp2*0.4})`;ctx.lineWidth=1.5;ctx.stroke();
const axStart=hy+12;ctx.beginPath();ctx.moveTo(somaX,axStart);ctx.lineTo(somaX,CH-50);ctx.strokeStyle='rgba(200,200,220,0.2)';ctx.lineWidth=3;ctx.stroke();
for(let i=0;i<5;i++){const sy=axStart+30+i*(CH-50-axStart-30)/5;ctx.fillStyle='rgba(180,180,200,0.1)';ctx.fillRect(somaX-9,sy-12,18,24);ctx.strokeStyle='rgba(180,180,200,0.15)';ctx.lineWidth=1;ctx.strokeRect(somaX-9,sy-12,18,24);}
if(ns.firing||ns.lastSpikeTime>ts-300){const el=ts-ns.lastSpikeTime,td=Math.min(el/300,1.0),py=axStart+td*(CH-50-axStart),pa=td<0.1?td/0.1:(1-td)/0.9;const g2=ctx.createRadialGradient(somaX,py,2,somaX,py,18);g2.addColorStop(0,`rgba(229,192,123,${Math.max(0,pa)})`);g2.addColorStop(1,'rgba(229,192,123,0)');ctx.beginPath();ctx.arc(somaX,py,18,0,Math.PI*2);ctx.fillStyle=g2;ctx.fill();}
const gx=55,gy2=CH-120,gw=330,gh=90;ctx.fillStyle='rgba(0,0,0,0.5)';ctx.fillRect(gx,gy2,gw,gh);ctx.strokeStyle='rgba(255,255,255,0.1)';ctx.strokeRect(gx,gy2,gw,gh);
const threshY=gy2+gh*0.45;ctx.beginPath();ctx.moveTo(gx,threshY);ctx.lineTo(gx+gw,threshY);ctx.setLineDash([3,3]);ctx.strokeStyle='rgba(255,255,255,0.3)';ctx.stroke();ctx.setLineDash([]);
if(ns.ltpLevel>0.05){const lty=gy2+gh*0.25;ctx.beginPath();ctx.moveTo(gx,lty);ctx.lineTo(gx+gw,lty);ctx.setLineDash([2,5]);ctx.strokeStyle=`rgba(198,120,221,${0.3+ns.ltpLevel*0.4})`;ctx.stroke();ctx.setLineDash([]);}
const barH=Math.max(0,vNorm*gh*0.55),barY=threshY-barH;let barC=ns.v>=-55?'rgba(229,192,123,0.7)':'rgba(78,201,176,0.5)';if(ns.absRefractory>0)barC='rgba(244,71,107,0.6)';else if(ns.relRefractory>0)barC='rgba(229,192,123,0.5)';ctx.fillStyle=barC;ctx.fillRect(gx+10,barY,gw-20,threshY-barY);
if(ns.absRefractory>0||ns.relRefractory>0){ctx.fillStyle='rgba(244,71,107,0.08)';ctx.fillRect(gx+10,gy2+4,gw-20,gh*0.25);}
ctx.fillStyle='rgba(255,255,255,0.4)';ctx.font='9px "SF Mono","Fira Code",monospace';ctx.textAlign='left';
ctx.fillText('Vₘ (mV)',gx+4,gy2+10);ctx.fillText('threshold',gx+4,threshY-4);if(ns.ltpLevel>0.05){ctx.fillStyle='rgba(198,120,221,0.7)';ctx.fillText('LTP-lowered',gx+4,gy2+gh*0.25-4);}
ctx.fillText(`Vₘ = ${ns.v.toFixed(0)} mV`,gx+gw-90,gy2+10);ctx.fillText(`LTP: ${(ns.ltpLevel*100).toFixed(0)}%`,gx+gw-90,gy2+gh-6);
ctx.fillStyle='rgba(255,255,255,0.3)';ctx.font='11px "SF Mono","Fira Code",monospace';ctx.textAlign='center';
let st=lang==='zh'?'静息':'';if(ns.firing)st=lang==='zh'?'🔥 发放!':'🔥 FIRING!';else if(ns.absRefractory>0)st=lang==='zh'?'🔒 绝对不应期':'🔒 Absolute Refractory';else if(ns.relRefractory>0)st=lang==='zh'?'🔓 相对不应期':'🔓 Relative Refractory';else if(vNorm>0.5)st=lang==='zh'?'去极化中...':'Depolarizing...';ctx.fillText(st,somaX,CH-8);
}
function renderAll(){
renderFF();
document.getElementById('page-title').textContent=t('pageTitle');document.getElementById('intro-text').innerHTML=t('intro');
document.getElementById('left-title').textContent=t('leftTitle');document.getElementById('right-title').textContent=t('rightTitle');
document.getElementById('nlegend').innerHTML=t('nlegend');document.getElementById('explain-title').textContent=t('explainTitle');
document.getElementById('explain-text').innerHTML=t(`explain.${ffType}`);document.getElementById('breaks-title').textContent=t('breaksTitle');
document.getElementById('breaks-text').innerHTML=t(`breaks.${ffType}`);
document.getElementById('btn-zh').classList.toggle('active',lang==='zh');document.getElementById('btn-en').classList.toggle('active',lang==='en');
document.documentElement.lang=lang==='zh'?'zh':'en';
}
function setLang(l){lang=l;localStorage.setItem('ntd-lang',l);renderAll();}
document.getElementById('ff-sel').addEventListener('click',e=>{const b=e.target.closest('.ff-btn');if(!b)return;document.querySelectorAll('#ff-sel .ff-btn').forEach(x=>x.classList.remove('active'));b.classList.add('active');ffType=b.dataset.ff;q=0;sActive=false;rActive=false;ns={v:-70,firing:false,absRefractory:0,relRefractory:0,ltpLevel:0,lastSpikeTime:-1000,spikeHistory:[]};renderAll();});
document.getElementById('btn-zh').addEventListener('click',()=>setLang('zh'));
document.getElementById('btn-en').addEventListener('click',()=>setLang('en'));
function animate(ts){drawNeuron(ts);requestAnimationFrame(animate);}
renderAll();requestAnimationFrame(animate);
</script>
</body>
</html>