chihsing commited on
Commit
2a1ec4d
·
verified ·
1 Parent(s): c18ace5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +369 -18
index.html CHANGED
@@ -1,19 +1,370 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-TW">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>TaiScience CSD-EWS: Pole & Damping Ratio Simulator</title>
7
+ <style>
8
+ body { background-color: #121212; color: #e0e0e0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; line-height: 1.6; }
9
+ .container { max-width: 1200px; margin: 0 auto; }
10
+ .header { text-align: center; margin-bottom: 20px; border-bottom: 1px solid #333; padding-bottom: 15px; position: relative; }
11
+ .header h1 { margin: 0; color: #4dabf7; font-size: 28px; font-weight: 600; letter-spacing: 1px; }
12
+ .header p { color: #aaa; font-size: 15px; margin-top: 8px; max-width: 900px; margin-left: auto; margin-right: auto; }
13
+ .lang-switch { position: absolute; top: 0; right: 0; display: flex; gap: 6px; }
14
+ .lang-switch button {
15
+ background: #252525; color: #aaa; border: 1px solid #444; border-radius: 6px;
16
+ padding: 6px 12px; cursor: pointer; font-size: 13px; transition: all 0.2s;
17
+ }
18
+ .lang-switch button:hover { border-color: #4dabf7; color: #e0e0e0; }
19
+ .lang-switch button.active { background: #4dabf7; color: #121212; border-color: #4dabf7; font-weight: 600; }
20
+ .grid { display: grid; grid-template-columns: 1fr 2fr; gap: 20px; }
21
+ .controls { background: #1e1e1e; padding: 25px; border-radius: 8px; border: 1px solid #333; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
22
+ .visuals { display: flex; flex-direction: column; gap: 20px; }
23
+ .panel { background: #1e1e1e; padding: 20px; border-radius: 8px; border: 1px solid #333; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
24
+ h3 { margin-top: 0; color: #fff; font-size: 18px; border-bottom: 1px solid #444; padding-bottom: 10px; margin-bottom: 20px; }
25
+ label { display: block; margin-top: 20px; margin-bottom: 8px; font-weight: 500; font-size: 15px; color: #ccc; }
26
+ span.val { float: right; color: #4dabf7; font-weight: bold; }
27
+ input[type=range] { width: 100%; cursor: pointer; accent-color: #4dabf7; }
28
+ canvas { width: 100%; height: 260px; background: #0a0a0a; border: 1px solid #444; border-radius: 6px; }
29
+ .metric { font-size: 1.3em; font-weight: bold; margin-top: 30px; padding: 20px; background: #252525; border-radius: 8px; text-align: center; border: 2px solid #333; transition: border-color 0.3s; }
30
+ .metric .zeta-row { margin-bottom: 10px; }
31
+ .metric .status-label { font-size: 0.8em; color: #888; }
32
+ .safe { color: #51cf66; }
33
+ .warning { color: #fcc419; }
34
+ .danger { color: #ff6b6b; }
35
+ .math-notes { color: #888; font-size: 13px; margin-top: 30px; background: #1a1a1a; padding: 15px; border-radius: 6px; border-left: 3px solid #4dabf7; }
36
+ .math-notes .disclaimer { display: block; margin-top: 12px; color: #666; font-style: italic; }
37
+ .time-caption { font-size: 12px; color: #666; margin: -12px 0 10px 0; }
38
+ @media (max-width: 800px) {
39
+ .grid { grid-template-columns: 1fr; }
40
+ .lang-switch { position: static; justify-content: center; margin-bottom: 12px; }
41
+ }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <div class="container">
46
+ <div class="header">
47
+ <div class="lang-switch">
48
+ <button type="button" id="lang-zh" class="active" aria-pressed="true">中文</button>
49
+ <button type="button" id="lang-en" aria-pressed="false">English</button>
50
+ </div>
51
+ <h1 id="title-main">TaiScience CSD-EWS</h1>
52
+ <p id="title-sub">動態系統極點與阻尼比實時模擬器 | Jacobian 特徵值代理特徵(教學示範)</p>
53
+ </div>
54
+ <div class="grid">
55
+ <div class="controls">
56
+ <h3 id="controls-heading">系統特徵值參數 (System Poles)</h3>
57
+
58
+ <label id="label-sigma">實部 (σ, 衰減率/生長率): <span class="val" id="sigma-val">-0.50</span></label>
59
+ <input type="range" id="sigma" min="-2.0" max="0.5" step="0.01" value="-0.5">
60
+
61
+ <label id="label-omega">虛部 (ω, 振盪頻率): <span class="val" id="omega-val">5.0</span></label>
62
+ <input type="range" id="omega" min="1.0" max="15.0" step="0.1" value="5.0">
63
+
64
+ <div class="metric" id="status-box">
65
+ <div class="zeta-row"><span id="zeta-label">阻尼比 (ζ):</span> <span id="zeta-val" style="font-size: 1.2em;">0.1000</span></div>
66
+ <div class="status-label" id="status-label">狀態判別 (ζ 閾值 5%):</div>
67
+ <div id="status-text" class="safe" style="margin-top: 5px;"></div>
68
+ </div>
69
+
70
+ <div class="math-notes" id="math-notes"></div>
71
+ </div>
72
+
73
+ <div class="visuals">
74
+ <div class="panel">
75
+ <h3 id="splane-heading">複數平面 (s-plane) 映射</h3>
76
+ <canvas id="s-plane"></canvas>
77
+ </div>
78
+ <div class="panel">
79
+ <h3 id="time-heading">時域響應(示意波形)</h3>
80
+ <p class="time-caption" id="time-caption"></p>
81
+ <canvas id="time-domain"></canvas>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <script>
88
+ const ZETA_WEAK_THRESHOLD = 0.05;
89
+
90
+ const I18N = {
91
+ 'zh-TW': {
92
+ pageTitle: 'TaiScience CSD-EWS: 動態系統極點與阻尼比分析',
93
+ titleMain: 'TaiScience CSD-EWS',
94
+ titleSub: '動態系統極點與阻尼比實時模擬器 | Jacobian 特徵值代理特徵(教學示範)',
95
+ controlsHeading: '系統特徵值參數 (System Poles)',
96
+ labelSigma: '實部 (σ, 衰減率/生長率):',
97
+ labelOmega: '虛部 (ω, 振盪頻率):',
98
+ zetaLabel: '阻尼比 (ζ):',
99
+ statusLabel: '狀態判別 (ζ 閾值 5%):',
100
+ statusDanger: '不穩定 / 負阻尼 (σ > 0)',
101
+ statusWarning: '弱阻尼 (ζ < 5%)',
102
+ statusSafe: '安全阻尼 (ζ ≥ 5%)',
103
+ mathNotes: '<strong>核心公式(共軛極點假設):</strong><br><br>'
104
+ + '1. <strong>特徵值:</strong> s = σ ± jω<br>'
105
+ + '2. <strong>阻尼比:</strong> ζ = −σ / √(σ² + ω²)<br>'
106
+ + '3. <strong>臨界邊界:</strong> σ 越過 0 進入右半平面時 ζ &lt; 0,系統發散。<br>'
107
+ + '4. <strong>告警閾值:</strong> 極點顏色、狀態與時域曲線均以同一規則判斷:σ &gt; 0 → 危險;0 ≤ ζ &lt; 0.05 → 警告;ζ ≥ 0.05 → 安全。<br>'
108
+ + '<span class="disclaimer">本頁為手動調參的互動教具,非 PMU / Toeplitz 即時辨識輸出;時域圖僅示範 e<sup>σt</sup>cos(ωt),不含模態振幅與相位。</span>',
109
+ splaneHeading: '複數平面 (s-plane) 映射',
110
+ timeHeading: '時域響應(示意波形)',
111
+ timeCaption: 'y(t) = e<sup>σt</sup> cos(ωt) — 固定單位振幅,僅供直覺理解衰減與頻率',
112
+ axisRe: 'Re (σ)',
113
+ axisIm: 'Im (jω)',
114
+ polePos: 's = σ + jω',
115
+ poleNeg: 's = σ − jω',
116
+ collapseWarn: '⚠ 振幅超出顯示範圍'
117
+ },
118
+ en: {
119
+ pageTitle: 'TaiScience CSD-EWS: Pole & Damping Ratio Simulator',
120
+ titleMain: 'TaiScience CSD-EWS',
121
+ titleSub: 'Real-time pole & damping ratio simulator | Jacobian eigenvalue proxy (educational demo)',
122
+ controlsHeading: 'System Eigenvalue Parameters (Poles)',
123
+ labelSigma: 'Real part (σ, decay/growth rate):',
124
+ labelOmega: 'Imaginary part (ω, oscillation frequency):',
125
+ zetaLabel: 'Damping ratio (ζ):',
126
+ statusLabel: 'Status (ζ threshold 5%):',
127
+ statusDanger: 'Unstable / negative damping (σ > 0)',
128
+ statusWarning: 'Weak damping (ζ < 5%)',
129
+ statusSafe: 'Adequate damping (ζ ≥ 5%)',
130
+ mathNotes: '<strong>Core formulas (conjugate-pole assumption):</strong><br><br>'
131
+ + '1. <strong>Eigenvalues:</strong> s = σ ± jω<br>'
132
+ + '2. <strong>Damping ratio:</strong> ζ = −σ / √(σ² + ω²)<br>'
133
+ + '3. <strong>Stability boundary:</strong> when σ crosses 0 into the RHP, ζ &lt; 0 and the mode diverges.<br>'
134
+ + '4. <strong>Alert rule:</strong> pole color, status, and waveform share one rule: σ &gt; 0 → danger; 0 ≤ ζ &lt; 0.05 → warning; ζ ≥ 0.05 → safe.<br>'
135
+ + '<span class="disclaimer">This page is a manual teaching demo, not live PMU / Toeplitz identification. The waveform shows e<sup>σt</sup>cos(ωt) only—no modal amplitude or phase.</span>',
136
+ splaneHeading: 'Complex s-plane map',
137
+ timeHeading: 'Time-domain response (illustrative)',
138
+ timeCaption: 'y(t) = e<sup>σt</sup> cos(ωt) — unit amplitude fixed; for intuition only',
139
+ axisRe: 'Re (σ)',
140
+ axisIm: 'Im (jω)',
141
+ polePos: 's = σ + jω',
142
+ poleNeg: 's = σ − jω',
143
+ collapseWarn: '⚠ Amplitude exceeds plot range'
144
+ }
145
+ };
146
+
147
+ let lang = localStorage.getItem('csd-sim-lang') || 'zh-TW';
148
+ if (!I18N[lang]) lang = 'zh-TW';
149
+
150
+ const sigmaSlider = document.getElementById('sigma');
151
+ const omegaSlider = document.getElementById('omega');
152
+ const sigmaVal = document.getElementById('sigma-val');
153
+ const omegaVal = document.getElementById('omega-val');
154
+ const zetaVal = document.getElementById('zeta-val');
155
+ const statusText = document.getElementById('status-text');
156
+ const statusBox = document.getElementById('status-box');
157
+ const splaneCanvas = document.getElementById('s-plane');
158
+ const ctxS = splaneCanvas.getContext('2d');
159
+ const timeCanvas = document.getElementById('time-domain');
160
+ const ctxT = timeCanvas.getContext('2d');
161
+
162
+ function t(key) {
163
+ return I18N[lang][key];
164
+ }
165
+
166
+ /** Unified status: danger | warning | safe */
167
+ function getStatus(sigma, zeta) {
168
+ if (sigma > 0 || zeta < 0) {
169
+ return { level: 'danger', color: '#ff6b6b', message: t('statusDanger') };
170
+ }
171
+ if (zeta < ZETA_WEAK_THRESHOLD) {
172
+ return { level: 'warning', color: '#fcc419', message: t('statusWarning') };
173
+ }
174
+ return { level: 'safe', color: '#51cf66', message: t('statusSafe') };
175
+ }
176
+
177
+ function setLabelHtml(id, text) {
178
+ const el = document.getElementById(id);
179
+ const valSpan = el.querySelector('.val');
180
+ const valId = valSpan ? valSpan.id : null;
181
+ const valText = valSpan ? valSpan.textContent : '';
182
+ el.innerHTML = text + (valSpan ? ' <span class="val" id="' + valId + '">' + valText + '</span>' : '');
183
+ }
184
+
185
+ function applyLanguage() {
186
+ const L = I18N[lang];
187
+ document.documentElement.lang = lang === 'en' ? 'en' : 'zh-TW';
188
+ document.title = L.pageTitle;
189
+ document.getElementById('title-main').textContent = L.titleMain;
190
+ document.getElementById('title-sub').textContent = L.titleSub;
191
+ document.getElementById('controls-heading').textContent = L.controlsHeading;
192
+ setLabelHtml('label-sigma', L.labelSigma);
193
+ setLabelHtml('label-omega', L.labelOmega);
194
+ document.getElementById('zeta-label').textContent = L.zetaLabel;
195
+ document.getElementById('status-label').textContent = L.statusLabel;
196
+ document.getElementById('math-notes').innerHTML = L.mathNotes;
197
+ document.getElementById('splane-heading').textContent = L.splaneHeading;
198
+ document.getElementById('time-heading').textContent = L.timeHeading;
199
+ document.getElementById('time-caption').innerHTML = L.timeCaption;
200
+ document.getElementById('lang-zh').classList.toggle('active', lang === 'zh-TW');
201
+ document.getElementById('lang-en').classList.toggle('active', lang === 'en');
202
+ document.getElementById('lang-zh').setAttribute('aria-pressed', lang === 'zh-TW');
203
+ document.getElementById('lang-en').setAttribute('aria-pressed', lang === 'en');
204
+ localStorage.setItem('csd-sim-lang', lang);
205
+ draw();
206
+ }
207
+
208
+ function resizeCanvases() {
209
+ splaneCanvas.width = splaneCanvas.clientWidth * window.devicePixelRatio;
210
+ splaneCanvas.height = splaneCanvas.clientHeight * window.devicePixelRatio;
211
+ timeCanvas.width = timeCanvas.clientWidth * window.devicePixelRatio;
212
+ timeCanvas.height = timeCanvas.clientHeight * window.devicePixelRatio;
213
+ ctxS.setTransform(1, 0, 0, 1, 0, 0);
214
+ ctxT.setTransform(1, 0, 0, 1, 0, 0);
215
+ ctxS.scale(window.devicePixelRatio, window.devicePixelRatio);
216
+ ctxT.scale(window.devicePixelRatio, window.devicePixelRatio);
217
+ draw();
218
+ }
219
+ window.addEventListener('resize', resizeCanvases);
220
+
221
+ function drawSPlane(sigma, omega, poleColor) {
222
+ const w = splaneCanvas.clientWidth;
223
+ const h = splaneCanvas.clientHeight;
224
+ if (w <= 0 || h <= 0) return;
225
+ ctxS.clearRect(0, 0, w, h);
226
+
227
+ const originX = w * 0.75;
228
+ const originY = h / 2;
229
+
230
+ ctxS.fillStyle = 'rgba(43, 138, 62, 0.15)';
231
+ ctxS.fillRect(0, 0, originX, h);
232
+ ctxS.fillStyle = 'rgba(201, 42, 42, 0.15)';
233
+ ctxS.fillRect(originX, 0, w - originX, h);
234
+
235
+ ctxS.strokeStyle = '#333';
236
+ ctxS.lineWidth = 1;
237
+ ctxS.beginPath();
238
+ for (let i = 0; i < w; i += 40) { ctxS.moveTo(i, 0); ctxS.lineTo(i, h); }
239
+ for (let i = 0; i < h; i += 40) { ctxS.moveTo(0, i); ctxS.lineTo(w, i); }
240
+ ctxS.stroke();
241
+
242
+ ctxS.beginPath();
243
+ ctxS.moveTo(0, originY); ctxS.lineTo(w, originY);
244
+ ctxS.moveTo(originX, 0); ctxS.lineTo(originX, h);
245
+ ctxS.strokeStyle = '#888';
246
+ ctxS.lineWidth = 2;
247
+ ctxS.stroke();
248
+
249
+ ctxS.fillStyle = '#aaa';
250
+ ctxS.font = '12px sans-serif';
251
+ ctxS.fillText(t('axisRe'), w - 40, originY - 10);
252
+ ctxS.fillText(t('axisIm'), originX + 10, 20);
253
+
254
+ const scaleX = 80;
255
+ const scaleY = 7;
256
+ const px = originX + sigma * scaleX;
257
+ const py1 = originY - omega * scaleY;
258
+ const py2 = originY + omega * scaleY;
259
+
260
+ function drawCross(x, y, color) {
261
+ ctxS.beginPath();
262
+ ctxS.moveTo(x - 6, y - 6); ctxS.lineTo(x + 6, y + 6);
263
+ ctxS.moveTo(x - 6, y + 6); ctxS.lineTo(x + 6, y - 6);
264
+ ctxS.strokeStyle = color;
265
+ ctxS.lineWidth = 3;
266
+ ctxS.stroke();
267
+ }
268
+
269
+ drawCross(px, py1, poleColor);
270
+ drawCross(px, py2, poleColor);
271
+
272
+ ctxS.fillStyle = '#fff';
273
+ ctxS.font = '14px sans-serif';
274
+ ctxS.fillText(t('polePos'), px + 15, py1 + 5);
275
+ ctxS.fillText(t('poleNeg'), px + 15, py2 + 5);
276
+
277
+ ctxS.beginPath();
278
+ ctxS.setLineDash([5, 5]);
279
+ ctxS.moveTo(originX, originY);
280
+ ctxS.lineTo(px, py1);
281
+ ctxS.moveTo(originX, originY);
282
+ ctxS.lineTo(px, py2);
283
+ ctxS.strokeStyle = poleColor;
284
+ ctxS.lineWidth = 1.5;
285
+ ctxS.stroke();
286
+ ctxS.setLineDash([]);
287
+ }
288
+
289
+ function drawTimeDomain(sigma, omega, strokeColor) {
290
+ const w = timeCanvas.clientWidth;
291
+ const h = timeCanvas.clientHeight;
292
+ if (w <= 0 || h <= 0) return;
293
+ ctxT.clearRect(0, 0, w, h);
294
+
295
+ const originY = h / 2;
296
+
297
+ ctxT.strokeStyle = '#222';
298
+ ctxT.lineWidth = 1;
299
+ ctxT.beginPath();
300
+ for (let i = 0; i < w; i += 50) { ctxT.moveTo(i, 0); ctxT.lineTo(i, h); }
301
+ for (let i = 0; i < h; i += 40) { ctxT.moveTo(0, i); ctxT.lineTo(w, i); }
302
+ ctxT.stroke();
303
+
304
+ ctxT.beginPath();
305
+ ctxT.moveTo(0, originY); ctxT.lineTo(w, originY);
306
+ ctxT.strokeStyle = '#666';
307
+ ctxT.lineWidth = 2;
308
+ ctxT.stroke();
309
+
310
+ const timeDuration = 10;
311
+ let outOfBounds = false;
312
+
313
+ ctxT.beginPath();
314
+ for (let px = 0; px <= w; px++) {
315
+ const tSec = (px / w) * timeDuration;
316
+ const y = Math.exp(sigma * tSec) * Math.cos(omega * tSec);
317
+ const py = originY - (y * (h / 4));
318
+ if (py < -h || py > h * 2) outOfBounds = true;
319
+ if (px === 0) ctxT.moveTo(px, py);
320
+ else ctxT.lineTo(px, py);
321
+ }
322
+
323
+ ctxT.strokeStyle = strokeColor;
324
+ ctxT.lineWidth = 3;
325
+ ctxT.stroke();
326
+
327
+ if (outOfBounds) {
328
+ ctxT.fillStyle = 'rgba(255, 107, 107, 0.8)';
329
+ ctxT.font = 'bold 16px sans-serif';
330
+ ctxT.fillText(t('collapseWarn'), 20, 30);
331
+ }
332
+ }
333
+
334
+ function draw() {
335
+ const sigma = parseFloat(sigmaSlider.value);
336
+ const omega = parseFloat(omegaSlider.value);
337
+ const mag = Math.sqrt(sigma * sigma + omega * omega);
338
+ const zeta = mag > 0 ? -sigma / mag : 0;
339
+ const status = getStatus(sigma, zeta);
340
+
341
+ document.getElementById('sigma-val').textContent = sigma.toFixed(2);
342
+ document.getElementById('omega-val').textContent = omega.toFixed(1);
343
+ zetaVal.textContent = zeta.toFixed(4);
344
+ zetaVal.style.color = status.color;
345
+
346
+ statusText.textContent = status.message;
347
+ statusText.className = status.level;
348
+ statusBox.style.borderColor = status.color;
349
+
350
+ drawSPlane(sigma, omega, status.color);
351
+ drawTimeDomain(sigma, omega, status.color);
352
+ }
353
+
354
+ sigmaSlider.addEventListener('input', draw);
355
+ omegaSlider.addEventListener('input', draw);
356
+
357
+ document.getElementById('lang-zh').addEventListener('click', () => {
358
+ if (lang !== 'zh-TW') { lang = 'zh-TW'; applyLanguage(); }
359
+ });
360
+ document.getElementById('lang-en').addEventListener('click', () => {
361
+ if (lang !== 'en') { lang = 'en'; applyLanguage(); }
362
+ });
363
+
364
+ applyLanguage();
365
+ requestAnimationFrame(() => {
366
+ requestAnimationFrame(resizeCanvases);
367
+ });
368
+ </script>
369
+ </body>
370
  </html>