@jjlmoya/utils-hardware 1.19.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +4 -1
  4. package/src/index.ts +1 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/monitorGhostingTest/bibliography.astro +14 -0
  8. package/src/tool/monitorGhostingTest/bibliography.ts +20 -0
  9. package/src/tool/monitorGhostingTest/component.astro +156 -0
  10. package/src/tool/monitorGhostingTest/entry.ts +29 -0
  11. package/src/tool/monitorGhostingTest/i18n/de.ts +293 -0
  12. package/src/tool/monitorGhostingTest/i18n/en.ts +293 -0
  13. package/src/tool/monitorGhostingTest/i18n/es.ts +293 -0
  14. package/src/tool/monitorGhostingTest/i18n/fr.ts +293 -0
  15. package/src/tool/monitorGhostingTest/i18n/id.ts +293 -0
  16. package/src/tool/monitorGhostingTest/i18n/it.ts +293 -0
  17. package/src/tool/monitorGhostingTest/i18n/ja.ts +293 -0
  18. package/src/tool/monitorGhostingTest/i18n/ko.ts +293 -0
  19. package/src/tool/monitorGhostingTest/i18n/nl.ts +293 -0
  20. package/src/tool/monitorGhostingTest/i18n/pl.ts +293 -0
  21. package/src/tool/monitorGhostingTest/i18n/pt.ts +293 -0
  22. package/src/tool/monitorGhostingTest/i18n/ru.ts +293 -0
  23. package/src/tool/monitorGhostingTest/i18n/sv.ts +293 -0
  24. package/src/tool/monitorGhostingTest/i18n/tr.ts +293 -0
  25. package/src/tool/monitorGhostingTest/i18n/zh.ts +293 -0
  26. package/src/tool/monitorGhostingTest/index.ts +9 -0
  27. package/src/tool/monitorGhostingTest/logic.ts +195 -0
  28. package/src/tool/monitorGhostingTest/monitor-ghosting-test.css +546 -0
  29. package/src/tool/monitorGhostingTest/seo.astro +15 -0
  30. package/src/tool/monitorGhostingTest/ui.ts +30 -0
  31. package/src/tools.ts +2 -1
@@ -0,0 +1,195 @@
1
+ export type GhostingBackground = 'dark' | 'light' | 'grid';
2
+ export type GhostingPattern = 'bars' | 'text' | 'ufo';
3
+
4
+ interface GhostingElements {
5
+ root: HTMLElement | null;
6
+ monitor: HTMLElement | null;
7
+ track: HTMLElement | null;
8
+ target: HTMLElement | null;
9
+ speed: HTMLInputElement | null;
10
+ trail: HTMLInputElement | null;
11
+ background: HTMLSelectElement | null;
12
+ pattern: HTMLSelectElement | null;
13
+ pursuit: HTMLInputElement | null;
14
+ fullscreen: HTMLElement | null;
15
+ pause: HTMLElement | null;
16
+ reset: HTMLElement | null;
17
+ speedValue: HTMLElement | null;
18
+ trailValue: HTMLElement | null;
19
+ blur: HTMLElement | null;
20
+ frameStep: HTMLElement | null;
21
+ persistence: HTMLElement | null;
22
+ sampleCount: HTMLElement | null;
23
+ }
24
+
25
+ interface GhostingLabels {
26
+ pixelsPerSecondUnit: string;
27
+ pixelUnit: string;
28
+ millisecondUnit: string;
29
+ fullscreen: string;
30
+ exitFullscreen: string;
31
+ pause: string;
32
+ resume: string;
33
+ }
34
+
35
+ export class MonitorGhostingLab {
36
+ private speed = 960;
37
+ private trail = 5;
38
+ private background: GhostingBackground = 'dark';
39
+ private pattern: GhostingPattern = 'bars';
40
+ private pursuit = true;
41
+ private paused = false;
42
+ private resizeObserver: ResizeObserver | null = null;
43
+
44
+ constructor(
45
+ private readonly elements: GhostingElements,
46
+ private readonly labels: GhostingLabels,
47
+ ) {
48
+ this.syncFromControls();
49
+ this.bindEvents();
50
+ this.observeMotionArea();
51
+ this.render();
52
+ }
53
+
54
+ private bindEvents() {
55
+ this.bindControl(this.elements.speed, 'input');
56
+ this.bindControl(this.elements.trail, 'input');
57
+ this.bindControl(this.elements.background, 'change');
58
+ this.bindControl(this.elements.pattern, 'change');
59
+ this.bindControl(this.elements.pursuit, 'change');
60
+ this.bindClick(this.elements.fullscreen, () => this.toggleFullscreen());
61
+ this.bindClick(this.elements.pause, () => this.togglePaused());
62
+ document.addEventListener('fullscreenchange', () => this.renderFullscreenLabel());
63
+ this.bindClick(this.elements.reset, () => this.reset());
64
+ }
65
+
66
+ private updateFromControls() {
67
+ this.syncFromControls();
68
+ this.render();
69
+ }
70
+
71
+ private syncFromControls() {
72
+ this.speed = this.getInputNumber(this.elements.speed, 960);
73
+ this.trail = this.getInputNumber(this.elements.trail, 5);
74
+ this.background = this.getSelectValue(this.elements.background, 'dark');
75
+ this.pattern = this.getSelectValue(this.elements.pattern, 'bars');
76
+ this.pursuit = this.elements.pursuit?.checked === true;
77
+ }
78
+
79
+ private bindControl(element: HTMLElement | null, eventName: 'change' | 'input') {
80
+ element?.addEventListener(eventName, () => this.updateFromControls());
81
+ }
82
+
83
+ private bindClick(element: HTMLElement | null, handler: () => void) {
84
+ element?.addEventListener('click', handler);
85
+ }
86
+
87
+ private getInputNumber(element: HTMLInputElement | null, fallback: number) {
88
+ return Number(element?.value ?? fallback);
89
+ }
90
+
91
+ private getSelectValue<T extends string>(element: HTMLSelectElement | null, fallback: T) {
92
+ return (element?.value ?? fallback) as T;
93
+ }
94
+
95
+ private reset() {
96
+ if (this.elements.speed) this.elements.speed.value = '960';
97
+ if (this.elements.trail) this.elements.trail.value = '5';
98
+ if (this.elements.background) this.elements.background.value = 'dark';
99
+ if (this.elements.pattern) this.elements.pattern.value = 'bars';
100
+ if (this.elements.pursuit) this.elements.pursuit.checked = true;
101
+ this.paused = false;
102
+ this.updateFromControls();
103
+ }
104
+
105
+ private render() {
106
+ this.renderRootState();
107
+ this.renderText();
108
+ this.renderFullscreenLabel();
109
+ this.renderPauseLabel();
110
+ }
111
+
112
+ private observeMotionArea() {
113
+ if (!this.elements.track || !this.elements.target) return;
114
+
115
+ this.resizeObserver = new ResizeObserver(() => this.renderRootState());
116
+ this.resizeObserver.observe(this.elements.track);
117
+ this.resizeObserver.observe(this.elements.target);
118
+ }
119
+
120
+ private async toggleFullscreen() {
121
+ if (document.fullscreenElement) {
122
+ await document.exitFullscreen();
123
+ return;
124
+ }
125
+
126
+ await this.elements.monitor?.requestFullscreen();
127
+ }
128
+
129
+ private togglePaused() {
130
+ this.paused = !this.paused;
131
+ this.render();
132
+ }
133
+
134
+ private getEstimatedBlur() {
135
+ return Math.round((this.speed / 240) * this.trail);
136
+ }
137
+
138
+ private getTravelDuration() {
139
+ const travelDistance = this.getTravelDistance();
140
+ const duration = travelDistance / this.speed;
141
+
142
+ return Math.max(0.35, Math.min(12, duration)).toFixed(2);
143
+ }
144
+
145
+ private getTravelDistance() {
146
+ const trackWidth = this.elements.track?.clientWidth ?? 0;
147
+ const targetWidth = this.elements.target?.getBoundingClientRect().width ?? 0;
148
+
149
+ return Math.max(120, trackWidth - targetWidth);
150
+ }
151
+
152
+ private getFrameStep() {
153
+ return Math.round((this.speed / 144) * 10) / 10;
154
+ }
155
+
156
+ private getPersistence() {
157
+ return Math.round((this.trail / 10) * 16.7 * 10) / 10;
158
+ }
159
+
160
+ private getSampleCount() {
161
+ return Math.max(3, Math.round(this.trail * 2.4));
162
+ }
163
+
164
+ private renderFullscreenLabel() {
165
+ this.setText(this.elements.fullscreen, document.fullscreenElement ? this.labels.exitFullscreen : this.labels.fullscreen);
166
+ }
167
+
168
+ private renderPauseLabel() {
169
+ this.setText(this.elements.pause, this.paused ? this.labels.resume : this.labels.pause);
170
+ }
171
+
172
+ private renderText() {
173
+ this.setText(this.elements.speedValue, `${this.speed} ${this.labels.pixelsPerSecondUnit}`);
174
+ this.setText(this.elements.trailValue, String(this.trail));
175
+ this.setText(this.elements.blur, `${this.getEstimatedBlur()} ${this.labels.pixelUnit}`);
176
+ this.setText(this.elements.frameStep, `${this.getFrameStep()} ${this.labels.pixelUnit}`);
177
+ this.setText(this.elements.persistence, `${this.getPersistence()} ${this.labels.millisecondUnit}`);
178
+ this.setText(this.elements.sampleCount, String(this.getSampleCount()));
179
+ }
180
+
181
+ private setText(element: HTMLElement | null, value: string) {
182
+ if (element) element.textContent = value;
183
+ }
184
+
185
+ private renderRootState() {
186
+ if (!this.elements.root) return;
187
+
188
+ this.elements.root.style.setProperty('--mgt-duration', `${this.getTravelDuration()}s`);
189
+ this.elements.root.style.setProperty('--mgt-trail-opacity', String(this.trail / 10));
190
+ this.elements.root.dataset.background = this.background;
191
+ this.elements.root.dataset.pattern = this.pattern;
192
+ this.elements.root.dataset.pursuit = this.pursuit ? 'on' : 'off';
193
+ this.elements.root.dataset.paused = this.paused ? 'true' : 'false';
194
+ }
195
+ }