Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
665 lines (610 loc) · 31.1 KB
/
Copy pathindex.html
File metadata and controls
665 lines (610 loc) · 31.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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>G474 HVCCPS Operator Console</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='5' fill='%23fff' stroke='%2317202a' stroke-width='2'/%3E%3Cpath d='M7 21V11M7 16h7M14 11v10M19 11l4 10 4-10' fill='none' stroke='%2317202a' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E">
<link rel="stylesheet" href="styles/index.css">
<script>
// Resolve the language before first paint so the page never renders in the
// wrong one, and arm a failsafe that reveals the shell even if the module
// graph fails to boot.
(function bootLanguage() {
try {
var stored = localStorage.getItem("hvccps.ui.language") || localStorage.getItem("hvccps-ui-language");
var lang = stored || ((navigator.language || "en").toLowerCase().indexOf("zh") === 0 ? "zh-CN" : "en");
document.documentElement.lang = lang;
document.documentElement.dataset.lang = lang;
} catch (error) { /* private mode: fall back to the markup default */ }
setTimeout(function () { document.documentElement.classList.add("is-ready"); }, 1500);
})();
</script>
<script src="vendor/chart.js/chart.umd.js"></script>
</head>
<body>
<div class="app-shell">
<!-- ============================ Top bar ============================ -->
<header class="topbar" id="topbar">
<div class="brand">
<span class="brand-mark">HV</span>
<span class="brand-text">
<strong>G474 HVCCPS</strong>
<em data-i18n="app.subtitle">Operator Console</em>
</span>
</div>
<button class="link-pill" type="button" data-el="linkPill"
data-i18n-attr="aria-label=a11y.openConnect" aria-label="Open serial link panel">
<span class="lamp" data-el="linkLamp"></span>
<span class="pill-text">
<strong data-el="linkLabel">DISCONNECTED</strong>
<em data-el="linkMeta">0 pkt/s</em>
</span>
</button>
<div class="topbar-gap"></div>
<nav class="nav-actions" data-i18n-attr="aria-label=a11y.primaryAreas" aria-label="Primary work areas">
<button class="tool-button" type="button" data-open="cycle" data-icon="wave">
<span data-i18n="nav.cycle">Cycle Plot</span>
</button>
<button class="tool-button" type="button" data-open="configure" data-icon="gear">
<span data-i18n="nav.configure">Configure</span>
</button>
<button class="tool-button" type="button" data-open="presets" data-icon="keys">
<span data-i18n="nav.presets">Presets</span>
</button>
<button class="tool-button" type="button" data-open="calibration" data-icon="ruler">
<span data-i18n="nav.calibrate">Calibrate</span>
</button>
<button class="tool-button" type="button" data-open="tools" data-icon="tools">
<span data-i18n="nav.tools">Tools</span>
</button>
</nav>
<div class="history-actions">
<button class="icon-button" type="button" data-el="undo" data-icon="undo" disabled
data-i18n-attr="aria-label=a11y.undo|data-tip=a11y.undo" aria-label="Undo"></button>
<button class="icon-button" type="button" data-el="redo" data-icon="redo" disabled
data-i18n-attr="aria-label=a11y.redo|data-tip=a11y.redo" aria-label="Redo"></button>
</div>
<label class="language-picker">
<span class="sr-only" data-i18n="topbar.language">Language</span>
<select data-el="language" data-i18n-attr="aria-label=topbar.language" aria-label="Language"></select>
</label>
</header>
<!-- ========================= Command bar ==========================
Always present, fixed height. This is the ONLY surface that reports
output state, so nothing appears or disappears when the output goes
live and the telemetry plot never moves.
================================================================== -->
<div class="command-bar" id="commandBar">
<div class="cb-state">
<span class="cb-lamp" data-el="lamp"></span>
<span class="cb-state-text">
<strong data-el="stateLabel">OUTPUT OFF</strong>
<em data-el="stateMeta">--</em>
</span>
</div>
<div class="cb-setpoints" data-i18n-attr="aria-label=cmd.setpoints" aria-label="Setpoints">
<label class="setpoint">
<span>CV</span>
<input type="number" data-el="cv" min="0" max="2200" step="1" inputmode="decimal">
<em>V</em>
</label>
<label class="setpoint">
<span>CC</span>
<input type="number" data-el="cc" min="0" max="200" step="0.1" inputmode="decimal">
<em>mA</em>
</label>
<label class="setpoint">
<span>CP</span>
<input type="number" data-el="cp" min="0" max="400" step="0.1" inputmode="decimal">
<em>W</em>
</label>
</div>
<div class="cb-readout">
<span data-el="readMode">--</span>
<span data-el="readTime">--</span>
<span data-el="readPower">--</span>
</div>
<button class="icon-button" type="button" data-open="run" data-icon="sliders"
data-i18n-attr="aria-label=cmd.runOptions|data-tip=cmd.runOptions" aria-label="Run options"></button>
<div class="cb-actions">
<button class="primary-button" type="button" data-el="primary" data-i18n="cmd.start">Start Output</button>
<button class="stop-button" type="button" data-el="stop" data-i18n="cmd.stop">STOP</button>
</div>
</div>
<!-- ========================== Workspace =========================== -->
<main class="workspace">
<section class="panel plot-panel" id="plotPanel" data-i18n-attr="aria-label=plot.title" aria-label="Telemetry plot">
<header class="panel-head">
<div class="panel-title">
<h1 data-i18n="plot.title">Telemetry Plot</h1>
<span data-el="subtitle">--</span>
</div>
<div class="panel-actions">
<button class="small-button" type="button" data-el="pickerToggle" data-icon="sliders" aria-expanded="false">
<span data-el="selectionSummary">--</span>
</button>
<button class="small-button" type="button" data-el="pause" data-icon="pause" aria-pressed="false">
<span data-i18n="plot.pause">Pause</span>
</button>
<button class="small-button" type="button" data-el="reset" data-icon="reset">
<span data-i18n="plot.default">Default</span>
</button>
</div>
</header>
<div class="chart-legend" data-el="legend" data-i18n-attr="aria-label=plot.legend" aria-label="Selected signals"></div>
<div class="chart-area">
<div class="chart-wrap">
<canvas data-el="canvas"></canvas>
<div class="chart-empty" data-el="empty" data-i18n="plot.noSignals" hidden>NO SIGNALS SELECTED</div>
</div>
<aside class="signal-picker" data-el="picker" hidden
data-i18n-attr="aria-label=signals.title" aria-label="Signal picker">
<div class="signal-picker-head">
<strong data-i18n="signals.title">Signals</strong>
<span data-el="pickerCount">--</span>
<button class="icon-button" type="button" data-el="pickerClose" data-icon="close"
data-i18n-attr="aria-label=signals.close" aria-label="Close signal picker"></button>
</div>
<label class="search-field">
<span data-i18n="signals.search">Search</span>
<input type="search" data-el="search" spellcheck="false" autocomplete="off"
data-i18n-attr="placeholder=signals.searchHint" placeholder="vsec, current, raw">
</label>
<div class="signal-picker-actions">
<button class="small-button" type="button" data-el="selectAll" data-i18n="signals.all">All</button>
<button class="small-button" type="button" data-el="clearAll" data-i18n="signals.clear">Clear</button>
</div>
<div class="signal-groups" data-el="groups"></div>
<div class="signal-no-match" data-el="noMatch" data-i18n="signals.noMatch" hidden>No matching signals</div>
</aside>
</div>
</section>
<aside class="panel status-panel" id="statusPanel"
data-i18n-attr="aria-label=status.title" aria-label="Power status">
<header class="panel-head panel-head-stack">
<div class="panel-title">
<h2 data-i18n="status.title">Power Status</h2>
<span data-el="subtitle">--</span>
</div>
<div class="status-chips" data-el="chips"></div>
</header>
<div class="status-scroll">
<div class="readings-key" data-el="keyReadings"></div>
<section class="readings-section">
<button class="section-toggle" type="button" data-el="secondaryToggle" aria-expanded="true">
<span data-i18n="status.secondary">Secondary Indicators</span>
<span class="chevron" data-icon="chevron"></span>
</button>
<div class="readings-list" data-el="secondary"></div>
</section>
<section class="readings-section">
<button class="section-toggle" type="button" data-el="debugToggle" aria-expanded="false">
<span data-i18n="status.debug">Raw / Debug</span>
<span class="chevron" data-icon="chevron"></span>
</button>
<div class="readings-list" data-el="debug" hidden></div>
</section>
</div>
</aside>
</main>
</div>
<div class="drawer-backdrop" id="drawerBackdrop" hidden></div>
<!-- =========================== Connect ============================= -->
<aside class="drawer drawer-narrow" id="connectDrawer" aria-hidden="true">
<header class="drawer-head">
<div>
<h2 data-i18n="connect.title">Serial Link</h2>
<span data-el="support">--</span>
</div>
<button class="icon-button" type="button" data-drawer-close data-icon="close"
data-i18n-attr="aria-label=a11y.closePanel" aria-label="Close panel"></button>
</header>
<div class="drawer-body">
<div class="info-row">
<span data-i18n="connect.port">Authorized Port</span>
<strong data-el="portInfo">--</strong>
</div>
<div class="action-grid">
<button class="secondary-button" type="button" data-el="selectPort" data-i18n="connect.selectPort">Select Port</button>
<button class="primary-button" type="button" data-el="connect" data-i18n="connect.connect">Connect</button>
<button class="danger-button" type="button" data-el="disconnect" data-i18n="connect.disconnect">Disconnect</button>
</div>
<div class="info-row">
<span data-i18n="connect.status">Status</span>
<strong data-el="status">--</strong>
</div>
<div class="info-row">
<span data-i18n="connect.rate">Packet Rate</span>
<strong data-el="rate">0 pkt/s</strong>
</div>
<p class="drawer-note" data-i18n="connect.note">
Connecting arms keep-alive: a read error, a closed link or a failed open all retry the same authorized port
automatically. Disconnect stops retrying.
</p>
</div>
</aside>
<!-- ========================= Run options =========================== -->
<aside class="drawer drawer-narrow" id="runDrawer" aria-hidden="true">
<header class="drawer-head">
<div>
<h2 data-i18n="run.title">Run Options</h2>
<span data-i18n="run.subtitle">duration and start mode</span>
</div>
<button class="icon-button" type="button" data-drawer-close data-icon="close"
data-i18n-attr="aria-label=a11y.closePanel" aria-label="Close panel"></button>
</header>
<div class="drawer-body">
<p class="drawer-note" data-i18n="run.note">
Targets live in the command bar and can be edited at any time. These options apply to the next start and are
locked while the output is live.
</p>
<fieldset class="form-group" data-el="durationGroup">
<legend data-i18n="run.duration">Duration</legend>
<div class="inline-grid">
<label class="field">
<span data-i18n="run.seconds">Seconds</span>
<input type="number" data-el="seconds" min="1" max="65534" step="1">
</label>
<label class="check-line">
<input type="checkbox" data-el="continuous">
<span data-i18n="run.continuous">Continuous</span>
</label>
</div>
</fieldset>
<fieldset class="form-group" data-el="modeGroup">
<legend data-i18n="run.startMode">Start Mode</legend>
<label class="radio-line">
<input type="radio" name="startMode" value="closed" data-el="modeClosed">
<span data-i18n="run.closedLoop">Closed-loop CC / CV / CP</span>
</label>
<label class="radio-line">
<input type="radio" name="startMode" value="fixed" data-el="modeFixed">
<span data-i18n="run.fixedDuty">Fixed duty debug</span>
</label>
<div class="fixed-duty-group" data-el="fixedGroup" hidden>
<label class="field">
<span data-i18n="run.fixedDutyValue">Fixed Duty</span>
<input type="number" data-el="fixedDuty" min="0" max="100" step="0.1">
<em>%</em>
</label>
<div class="warn-block" data-i18n="run.fixedDutyWarning">Fixed duty requires a verified load.</div>
</div>
</fieldset>
<div class="validation-line" data-el="validation" hidden></div>
</div>
</aside>
<!-- ========================= Configuration ========================= -->
<aside class="drawer drawer-wide" id="configureDrawer" aria-hidden="true">
<header class="drawer-head">
<div>
<h2 data-i18n="cfg.title">Configuration</h2>
<span data-el="state">--</span>
</div>
<button class="icon-button" type="button" data-drawer-close data-icon="close"
data-i18n-attr="aria-label=a11y.closePanel" aria-label="Close panel"></button>
</header>
<div class="configure-layout">
<nav class="configure-tabs" role="tablist" data-i18n-attr="aria-label=cfg.tabs" aria-label="Configuration tabs">
<button class="configure-tab is-active" type="button" role="tab" data-tab="loop" aria-selected="true"
data-i18n="cfg.tabLoop">Control Loop</button>
<button class="configure-tab" type="button" role="tab" data-tab="timing" aria-selected="false"
data-i18n="cfg.tabTiming">HRTIM</button>
<button class="configure-tab" type="button" role="tab" data-tab="device" aria-selected="false"
data-i18n="cfg.tabDevice">Device Constants</button>
</nav>
<div class="configure-pages">
<div class="configure-lock" data-el="lock" hidden>
<div>
<strong data-i18n="cfg.locked">Configuration is locked while the output is live</strong>
<button class="danger-button" type="button" data-el="lockStop" data-i18n="cmd.stop">STOP</button>
</div>
</div>
<section class="configure-page is-active" data-page="loop" role="tabpanel">
<div class="field-grid grid-3">
<label class="field"><span data-i18n="field.kpCv">Kp CV</span><input type="number" step="any" min="0" data-cfg="kpCv"></label>
<label class="field"><span data-i18n="field.kiCv">Ki CV</span><input type="number" step="any" min="0" data-cfg="kiCv"></label>
<label class="field"><span data-i18n="field.kpCc">Kp CC</span><input type="number" step="any" min="0" data-cfg="kpCc"></label>
<label class="field"><span data-i18n="field.kiCc">Ki CC</span><input type="number" step="any" min="0" data-cfg="kiCc"></label>
<label class="field"><span data-i18n="field.kpCp">Kp CP</span><input type="number" step="any" min="0" data-cfg="kpCp"></label>
<label class="field"><span data-i18n="field.kiCp">Ki CP</span><input type="number" step="any" min="0" data-cfg="kiCp"></label>
<label class="field"><span data-i18n="field.softStartStep">Soft-start Step</span><input type="number" step="any" min="0" max="1" data-cfg="softStartStep"></label>
</div>
</section>
<section class="configure-page" data-page="timing" role="tabpanel" hidden>
<div class="timing-summary">
<div><span data-i18n="cfg.freqSummary">Frequency (set · live)</span><strong data-el="freqSummary">--</strong></div>
<div><span data-i18n="cfg.deadTime">Dead Time</span><strong data-i18n="cfg.deadTimeValue">200 ns fixed</strong></div>
</div>
<div class="config-blocks">
<section class="config-block">
<h3 data-i18n="cfg.masterTiming">Master Timing</h3>
<label class="field">
<span data-i18n="field.baseFreqHz">Frequency (Hz)</span>
<input type="number" min="11000" max="45000" step="100" data-cfg="baseFreqHz">
</label>
<label class="field">
<span data-i18n="field.freqPolicy">Frequency Policy</span>
<select data-cfg="freqPolicy">
<option value="1" data-i18n="cfg.policyAuto">Auto</option>
<option value="0" data-i18n="cfg.policyFixed">Fixed</option>
</select>
</label>
<p class="config-note" data-i18n="cfg.freqNote">
Rated 11-45 kHz (fixed MUL4 prescaler); the firmware applies a new period reload on the fly.
24 ADC sub-period triggers fit each switching period.
</p>
</section>
<section class="config-block">
<h3 data-i18n="cfg.adcTitle">ADC Sampling (fixed)</h3>
<p class="config-note" data-i18n-html="cfg.adcNote"></p>
</section>
</div>
</section>
<section class="configure-page" data-page="device" role="tabpanel" hidden>
<div class="defaults-panel">
<strong data-i18n="cfg.deviceActions">Device configuration actions</strong>
<span data-i18n="cfg.deviceActionsNote">Draft changes stay in RAM until Save writes a full config record to flash.</span>
<div class="button-row">
<button class="secondary-button" type="button" data-el="sync" data-i18n="cfg.syncDraft">Sync Draft</button>
<button class="secondary-button" type="button" data-el="loadFlash" data-i18n="cfg.loadFlash">Load Flash</button>
<button class="secondary-button" type="button" data-el="loadDefaults" data-i18n="cfg.loadDefaults">Load Defaults</button>
<button class="danger-button" type="button" data-el="factoryReset" data-i18n="cfg.factoryReset">Factory Reset</button>
</div>
</div>
<div class="field-grid grid-3" data-el="deviceGrid"></div>
</section>
</div>
</div>
<footer class="drawer-footer">
<div class="validation-line" data-el="validation">--</div>
<button class="primary-button" type="button" data-el="apply" data-i18n="cfg.apply">Apply Settings</button>
<button class="secondary-button" type="button" data-el="save" data-i18n="cfg.save">Save Flash</button>
</footer>
</aside>
<!-- =========================== Presets ============================= -->
<aside class="drawer drawer-wide" id="presetsDrawer" aria-hidden="true">
<header class="drawer-head">
<div>
<h2 data-i18n="presets.title">Button Presets</h2>
<span data-el="state">--</span>
</div>
<button class="icon-button" type="button" data-drawer-close data-icon="close"
data-i18n-attr="aria-label=a11y.closePanel" aria-label="Close panel"></button>
</header>
<div class="drawer-body">
<p class="drawer-note" data-i18n="presets.intro">
Each front-panel key runs a stored closed-loop preset with no host attached. Press a key to start its preset;
while the output is live, pressing any key stops it. Saved presets persist in flash and are available right
after power-up.
</p>
<div class="lock-note" data-el="lockNote" hidden data-i18n="presets.locked">
Presets are locked while the output is live. Stop the output to edit them.
</div>
<div class="preset-columns" data-el="columns"></div>
<div class="validation-line" data-el="validation" hidden></div>
<div class="drawer-actions">
<button class="start-button" type="button" data-el="save" data-i18n="presets.save">Save Presets to Flash</button>
</div>
</div>
</aside>
<!-- ========================= Calibration =========================== -->
<aside class="drawer drawer-wide" id="calibrationDrawer" aria-hidden="true">
<header class="drawer-head">
<div>
<h2 data-i18n="cal.title">Output Calibration</h2>
<span data-el="state">--</span>
</div>
<button class="icon-button" type="button" data-drawer-close data-icon="close"
data-i18n-attr="aria-label=a11y.closePanel" aria-label="Close panel"></button>
</header>
<div class="drawer-body">
<p class="drawer-note" data-i18n="cal.intro">
Enter the uncalibrated RAW value reported by the device and the value an external meter measured. The host
compiles a residual table (exact at your points, linear between them, smoothly decaying to zero outside) and
streams it to a dedicated flash region. Corrections are clamped to ±50 V / ±50 mA in firmware. Writing and
enabling calibration touch flash, so the output must be off.
</p>
<div class="warn-block" data-i18n="cal.rawNote">
Collect points with Apply calibration OFF. Use RAW VSEC / RAW ISEC as the input columns, then enter the
external meter readings in the measured columns.
</div>
<div class="lock-note" data-el="lockNote" hidden data-i18n="cal.locked">
Calibration is locked while the output is live. Stop the output to edit it.
</div>
<div class="cal-status-row">
<div><span data-i18n="cal.table">Device table</span><strong data-el="statusTable">--</strong></div>
<div><span data-i18n="cal.crc">CRC</span><strong data-el="statusCrc">--</strong></div>
<div><span data-i18n="cal.applied">Applied</span><strong data-el="statusApplied">--</strong></div>
</div>
<label class="check-line boxed">
<input type="checkbox" data-el="enable">
<span data-i18n="cal.enable">Apply calibration to the control loop (saved to config flash)</span>
</label>
<fieldset class="form-group">
<legend data-i18n="cal.voltagePoints">Voltage points</legend>
<p class="hint" data-i18n="cal.voltageHint">RAW VSEC, meter V, and optionally meter current (mA) for load-dependent correction.</p>
<div class="cal-table">
<div class="cal-table-head">
<span data-i18n="cal.rawVsec">RAW VSEC (V)</span>
<span data-i18n="cal.meterV">Meter V</span>
<span data-i18n="cal.meterIOpt">Meter I (mA, opt)</span>
<span></span>
</div>
<div class="cal-rows" data-el="voltageRows"></div>
</div>
<button class="small-button" type="button" data-el="addVoltage" data-i18n="cal.addVoltage">+ Add voltage point</button>
</fieldset>
<fieldset class="form-group">
<legend data-i18n="cal.currentPoints">Current points</legend>
<p class="hint" data-i18n="cal.currentHint">RAW ISEC (mA) and meter I (mA), collected while calibration is disabled.</p>
<div class="cal-table">
<div class="cal-table-head cal-table-head-2">
<span data-i18n="cal.rawIsec">RAW ISEC (mA)</span>
<span data-i18n="cal.meterI">Meter I (mA)</span>
<span></span>
</div>
<div class="cal-rows" data-el="currentRows"></div>
</div>
<button class="small-button" type="button" data-el="addCurrent" data-i18n="cal.addCurrent">+ Add current point</button>
</fieldset>
<details class="cal-advanced">
<summary data-i18n="cal.advanced">Advanced: decay radius</summary>
<div class="inline-grid">
<label class="field"><span data-i18n="cal.radiusV">Voltage R (V)</span><input type="number" min="10" step="10" data-el="radiusV"></label>
<label class="field"><span data-i18n="cal.radiusI">Current R (mA)</span><input type="number" min="5" step="5" data-el="radiusI"></label>
</div>
<p class="hint" data-i18n="cal.radiusHint">A lone point fades to zero roughly R away from it. Larger R spreads each correction further.</p>
</details>
<div class="cal-preview" data-el="preview" hidden></div>
<div class="validation-line" data-el="validation" hidden></div>
<div class="drawer-actions cal-actions">
<button class="secondary-button" type="button" data-el="compile" data-i18n="cal.compile">Compile & Preview</button>
<button class="start-button" type="button" data-el="write" data-i18n="cal.write">Write Table to Flash</button>
</div>
<div class="cal-progress" data-el="progress" hidden>
<div class="cal-progress-track"><div class="cal-progress-bar" data-el="progressBar"></div></div>
<span data-el="progressText"></span>
</div>
</div>
</aside>
<!-- ============================ Tools ============================== -->
<aside class="drawer drawer-medium" id="toolsDrawer" aria-hidden="true">
<header class="drawer-head">
<div>
<h2 data-i18n="tools.title">Tools</h2>
<span data-i18n="tools.subtitle">plugins</span>
</div>
<button class="icon-button" type="button" data-drawer-close data-icon="close"
data-i18n-attr="aria-label=a11y.closePanel" aria-label="Close panel"></button>
</header>
<div class="tools-layout">
<nav class="tools-tabs" data-el="tabs" role="tablist"
data-i18n-attr="aria-label=tools.title" aria-label="Tools"></nav>
<div class="tools-body" data-el="body">
<div class="empty-state" data-el="empty" data-i18n="tools.empty">No tools registered.</div>
</div>
</div>
</aside>
<!-- ==================== Single-cycle waveform ====================== -->
<aside class="overlay" id="cycleOverlay" aria-hidden="true">
<div class="overlay-backdrop" data-el="backdrop"></div>
<section class="overlay-panel" role="dialog" aria-modal="true">
<header class="drawer-head">
<div>
<h2 data-i18n="cycle.title">Single-Cycle Waveform</h2>
<span data-el="subtitle" data-i18n="cycle.awaiting">awaiting telemetry</span>
</div>
<button class="icon-button" type="button" data-el="close" data-icon="close"
data-i18n-attr="aria-label=a11y.closePanel" aria-label="Close panel"></button>
</header>
<div class="overlay-body">
<aside class="cycle-side">
<section>
<h3 data-i18n="cycle.signals">Signals</h3>
<div class="cycle-signal-list" data-el="signalList"></div>
</section>
<section>
<h3 data-i18n="cycle.overlayGroup">Overlay</h3>
<label class="check-line">
<input type="checkbox" data-el="showPwm">
<span data-i18n="cycle.pwmBands">PWM edges (lead / lag bridge ON)</span>
</label>
<label class="check-line">
<span data-i18n="cycle.yMode">Y mode</span>
<select data-el="yMode">
<option value="normalized" data-i18n="cycle.yNormalized">Normalized 0..full-scale</option>
<option value="physical" data-i18n="cycle.yPhysical">Per-signal full-scale</option>
</select>
</label>
</section>
<section>
<h3 data-i18n="cycle.notes">Notes</h3>
<p class="cycle-note" data-i18n="cycle.note">
Plot shows the 24 raw samples per period straight from the device DMA, no smoothing. Slot 0 is at 0% of
T_sw, slot 23 is at 95.83%; there is no sample at 100% (the next cycle's slot 0 starts there). Each
rank's sample-and-hold happens at a small offset from the trigger; dots are placed at the true sample
instant, so you can see VPRI sit ~382 ns after VSEC inside the same trigger slot.
</p>
</section>
</aside>
<div class="cycle-canvas-wrap">
<canvas data-el="canvas"></canvas>
</div>
</div>
</section>
</aside>
<div class="toast-stack" id="toastStack" aria-live="polite" aria-atomic="false"></div>
<!-- =========================== Scripts ============================
Classic <script> tags, not ES modules: a page opened straight from
disk has the opaque file:// origin and the browser refuses to load
module scripts there. src/loader.js provides define()/require() so
the one-file-per-module structure survives. See src/loader.js.
================================================================= -->
<!-- Module registry first. Every file below only calls HV.define(), so tag order does not matter. -->
<script src="src/loader.js"></script>
<!-- core -->
<script src="src/core/bus.js"></script>
<script src="src/core/clipboard.js"></script>
<script src="src/core/dom.js"></script>
<script src="src/core/format.js"></script>
<script src="src/core/history.js"></script>
<script src="src/core/logger.js"></script>
<script src="src/core/notify.js"></script>
<script src="src/core/scheduler.js"></script>
<script src="src/core/storage.js"></script>
<!-- i18n -->
<script src="src/i18n/index.js"></script>
<script src="src/i18n/locales/en.js"></script>
<script src="src/i18n/locales/zh-CN.js"></script>
<!-- protocol -->
<script src="src/protocol/calibration.js"></script>
<script src="src/protocol/codec.js"></script>
<script src="src/protocol/command.js"></script>
<script src="src/protocol/config.js"></script>
<script src="src/protocol/constants.js"></script>
<script src="src/protocol/framing.js"></script>
<script src="src/protocol/heartbeat.js"></script>
<script src="src/protocol/index.js"></script>
<!-- device -->
<script src="src/device/calibration-service.js"></script>
<script src="src/device/config-service.js"></script>
<script src="src/device/cycle-signals.js"></script>
<script src="src/device/device-service.js"></script>
<script src="src/device/metrics.js"></script>
<script src="src/device/presets-service.js"></script>
<script src="src/device/run-service.js"></script>
<script src="src/device/serial-link.js"></script>
<script src="src/device/telemetry.js"></script>
<!-- ui -->
<script src="src/ui/app.js"></script>
<script src="src/ui/chart-selection.js"></script>
<script src="src/ui/components/confirm.js"></script>
<script src="src/ui/components/drawer-host.js"></script>
<script src="src/ui/components/number-guard.js"></script>
<script src="src/ui/components/toast.js"></script>
<script src="src/ui/components/validation.js"></script>
<script src="src/ui/registry.js"></script>
<script src="src/ui/views/command-bar.js"></script>
<script src="src/ui/views/cycle-overlay.js"></script>
<script src="src/ui/views/drawers/calibration.js"></script>
<script src="src/ui/views/drawers/configure.js"></script>
<script src="src/ui/views/drawers/connect.js"></script>
<script src="src/ui/views/drawers/presets.js"></script>
<script src="src/ui/views/drawers/run.js"></script>
<script src="src/ui/views/drawers/tools.js"></script>
<script src="src/ui/views/signal-picker.js"></script>
<script src="src/ui/views/status-panel.js"></script>
<script src="src/ui/views/telemetry-panel.js"></script>
<script src="src/ui/views/topbar.js"></script>
<!-- plugins -->
<script src="src/plugins/api.js"></script>
<script src="src/plugins/auto-test.js"></script>
<script src="src/plugins/frame-inspector.js"></script>
<script src="src/plugins/index.js"></script>
<script src="src/plugins/run-summary.js"></script>
<!-- entry point -->
<script src="src/main.js"></script>
<script>HV.start("main");</script>
</body>
</html>