@jjlmoya/utils-hardware 1.28.0 → 1.30.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.
- package/package.json +1 -1
- package/src/category/index.ts +3 -1
- package/src/entries.ts +7 -1
- package/src/index.ts +2 -0
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tool/mouseJitterAngleSnappingTest/bibliography.astro +14 -0
- package/src/tool/mouseJitterAngleSnappingTest/bibliography.ts +16 -0
- package/src/tool/mouseJitterAngleSnappingTest/component.astro +110 -0
- package/src/tool/mouseJitterAngleSnappingTest/entry.ts +29 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/de.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/en.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/es.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/fr.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/id.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/it.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/ja.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/ko.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/nl.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/pl.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/pt.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/ru.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/sv.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/tr.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/i18n/zh.ts +289 -0
- package/src/tool/mouseJitterAngleSnappingTest/index.ts +9 -0
- package/src/tool/mouseJitterAngleSnappingTest/logic.ts +245 -0
- package/src/tool/mouseJitterAngleSnappingTest/model.ts +38 -0
- package/src/tool/mouseJitterAngleSnappingTest/mouse-jitter-angle-snapping-test.css +412 -0
- package/src/tool/mouseJitterAngleSnappingTest/render.ts +48 -0
- package/src/tool/mouseJitterAngleSnappingTest/seo.astro +15 -0
- package/src/tool/mouseJitterAngleSnappingTest/ui.ts +29 -0
- package/src/tool/subwooferCrossoverTest/bibliography.astro +14 -0
- package/src/tool/subwooferCrossoverTest/bibliography.ts +16 -0
- package/src/tool/subwooferCrossoverTest/component.astro +253 -0
- package/src/tool/subwooferCrossoverTest/entry.ts +29 -0
- package/src/tool/subwooferCrossoverTest/i18n/de.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/en.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/es.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/fr.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/id.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/it.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/ja.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/ko.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/nl.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/pl.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/pt.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/ru.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/sv.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/tr.ts +188 -0
- package/src/tool/subwooferCrossoverTest/i18n/zh.ts +188 -0
- package/src/tool/subwooferCrossoverTest/index.ts +11 -0
- package/src/tool/subwooferCrossoverTest/logic.ts +30 -0
- package/src/tool/subwooferCrossoverTest/seo.astro +15 -0
- package/src/tool/subwooferCrossoverTest/subwoofer-crossover-test.css +282 -0
- package/src/tool/subwooferCrossoverTest/ui.ts +20 -0
- package/src/tools.ts +3 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { MAX_LOG, MAX_POINTS, WINDOW_SIZE, type Elements, type Labels, type PointSample, type TraceEvent, type TraceState } from './model';
|
|
2
|
+
import { drawGrid, drawNodes, drawTrace } from './render';
|
|
3
|
+
|
|
4
|
+
export class MouseJitterAngleSnappingTester {
|
|
5
|
+
private readonly points: PointSample[] = [];
|
|
6
|
+
private readonly events: TraceEvent[] = [];
|
|
7
|
+
private ctx: CanvasRenderingContext2D | null = null;
|
|
8
|
+
private raf = 0;
|
|
9
|
+
private dirty = true;
|
|
10
|
+
private active = false;
|
|
11
|
+
private nextPointFresh = true;
|
|
12
|
+
private sensitivity = 50;
|
|
13
|
+
private metrics = { jitter: 0, snapping: 0, straightness: 0, deviation: 0 };
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly elements: Elements,
|
|
17
|
+
private readonly labels: Labels,
|
|
18
|
+
) {
|
|
19
|
+
this.ctx = elements.canvas?.getContext('2d') ?? null;
|
|
20
|
+
this.sensitivity = Number(elements.sensitivity?.value ?? 50);
|
|
21
|
+
this.bind();
|
|
22
|
+
this.resize();
|
|
23
|
+
this.render();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private bind() {
|
|
27
|
+
window.addEventListener('resize', () => this.resize());
|
|
28
|
+
this.elements.stage?.addEventListener('pointerdown', (event) => this.start(event));
|
|
29
|
+
this.elements.stage?.addEventListener('pointermove', (event) => this.move(event));
|
|
30
|
+
this.elements.stage?.addEventListener('pointerup', () => this.stop());
|
|
31
|
+
this.elements.stage?.addEventListener('pointerleave', () => this.stop());
|
|
32
|
+
this.elements.sensitivity?.addEventListener('input', () => {
|
|
33
|
+
this.sensitivity = Number(this.elements.sensitivity?.value ?? 50);
|
|
34
|
+
this.analyze();
|
|
35
|
+
this.dirty = true;
|
|
36
|
+
this.renderPanel();
|
|
37
|
+
});
|
|
38
|
+
this.elements.reset?.addEventListener('click', () => this.reset());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private resize() {
|
|
42
|
+
const canvas = this.elements.canvas;
|
|
43
|
+
if (!canvas) return;
|
|
44
|
+
const rect = canvas.getBoundingClientRect();
|
|
45
|
+
const ratio = window.devicePixelRatio || 1;
|
|
46
|
+
canvas.width = Math.max(1, Math.floor(rect.width * ratio));
|
|
47
|
+
canvas.height = Math.max(1, Math.floor(rect.height * ratio));
|
|
48
|
+
this.ctx = canvas.getContext('2d');
|
|
49
|
+
this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
50
|
+
this.dirty = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private getCss(name: string, fallback: string) {
|
|
54
|
+
return getComputedStyle(this.elements.stage ?? document.documentElement).getPropertyValue(name).trim() || fallback;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private addPoint(event: PointerEvent) {
|
|
58
|
+
const canvas = this.elements.canvas;
|
|
59
|
+
if (!canvas) return;
|
|
60
|
+
const rect = canvas.getBoundingClientRect();
|
|
61
|
+
this.points.push({
|
|
62
|
+
x: event.clientX - rect.left,
|
|
63
|
+
y: event.clientY - rect.top,
|
|
64
|
+
t: performance.now(),
|
|
65
|
+
fresh: this.nextPointFresh,
|
|
66
|
+
});
|
|
67
|
+
this.nextPointFresh = false;
|
|
68
|
+
if (this.points.length > MAX_POINTS) this.points.splice(0, this.points.length - MAX_POINTS);
|
|
69
|
+
this.analyze();
|
|
70
|
+
this.dirty = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private draw() {
|
|
74
|
+
const canvas = this.elements.canvas;
|
|
75
|
+
const ctx = this.ctx;
|
|
76
|
+
if (!canvas || !ctx) return;
|
|
77
|
+
const width = canvas.clientWidth;
|
|
78
|
+
const height = canvas.clientHeight;
|
|
79
|
+
ctx.clearRect(0, 0, width, height);
|
|
80
|
+
drawGrid(ctx, width, height, (name, fallback) => this.getCss(name, fallback));
|
|
81
|
+
|
|
82
|
+
if (this.points.length > 1) {
|
|
83
|
+
const options = { points: this.points, jitter: this.metrics.jitter, getCss: (name: string, fallback: string) => this.getCss(name, fallback) };
|
|
84
|
+
drawTrace(ctx, options);
|
|
85
|
+
drawNodes(ctx, options);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.dirty = false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private start(event: PointerEvent) {
|
|
92
|
+
this.active = true;
|
|
93
|
+
this.nextPointFresh = true;
|
|
94
|
+
this.elements.stage?.setPointerCapture(event.pointerId);
|
|
95
|
+
this.addPoint(event);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private move(event: PointerEvent) {
|
|
99
|
+
if (!this.active) return;
|
|
100
|
+
const coalesced = typeof event.getCoalescedEvents === 'function' ? event.getCoalescedEvents() : [event];
|
|
101
|
+
coalesced.forEach((sample) => this.addPoint(sample));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private stop() {
|
|
105
|
+
this.active = false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private analyze() {
|
|
109
|
+
if (this.points.length < 4) {
|
|
110
|
+
this.resetMetrics();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const recent = this.points.slice(-Math.max(WINDOW_SIZE, Math.round(this.sensitivity * 0.7)));
|
|
115
|
+
const deviations = this.getLineDeviations(recent);
|
|
116
|
+
const averageDeviation = deviations.reduce((sum, value) => sum + value, 0) / Math.max(1, deviations.length);
|
|
117
|
+
const maxDeviation = Math.max(...deviations, 0);
|
|
118
|
+
const angles = this.getAngles(recent);
|
|
119
|
+
const straightness = this.getStraightness(angles);
|
|
120
|
+
const jitter = Math.min(100, Math.round((averageDeviation * 12 + maxDeviation * 3) * (100 / this.sensitivity)));
|
|
121
|
+
const snapping = Math.min(100, Math.round(straightness * 100));
|
|
122
|
+
|
|
123
|
+
this.metrics = { jitter, snapping, straightness: Math.round(straightness * 100), deviation: Math.round(averageDeviation * 10) / 10 };
|
|
124
|
+
|
|
125
|
+
this.recordEvent();
|
|
126
|
+
this.renderPanel();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private resetMetrics() {
|
|
130
|
+
this.metrics = { jitter: 0, snapping: 0, straightness: 0, deviation: 0 };
|
|
131
|
+
this.renderPanel();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private getLineDeviations(points: PointSample[]) {
|
|
135
|
+
const first = points[0];
|
|
136
|
+
const last = points[points.length - 1];
|
|
137
|
+
const dx = last.x - first.x;
|
|
138
|
+
const dy = last.y - first.y;
|
|
139
|
+
const length = Math.hypot(dx, dy);
|
|
140
|
+
if (length < 1) return [0];
|
|
141
|
+
return points.slice(1, -1).map((point) => Math.abs(dy * point.x - dx * point.y + last.x * first.y - last.y * first.x) / length);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private getAngles(points: PointSample[]) {
|
|
145
|
+
const angles: number[] = [];
|
|
146
|
+
for (let i = 1; i < points.length; i++) {
|
|
147
|
+
const dx = points[i].x - points[i - 1].x;
|
|
148
|
+
const dy = points[i].y - points[i - 1].y;
|
|
149
|
+
if (Math.hypot(dx, dy) >= 1.5) angles.push(Math.atan2(dy, dx));
|
|
150
|
+
}
|
|
151
|
+
return angles;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private getStraightness(angles: number[]) {
|
|
155
|
+
if (angles.length < 5) return 0;
|
|
156
|
+
const snapped = angles.filter((angle) => {
|
|
157
|
+
const degrees = Math.abs((angle * 180) / Math.PI);
|
|
158
|
+
const axis = Math.min(degrees % 90, 90 - (degrees % 90));
|
|
159
|
+
return axis <= 2.2;
|
|
160
|
+
}).length;
|
|
161
|
+
return snapped / angles.length;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private recordEvent() {
|
|
165
|
+
if (this.points.length % 18 !== 0) return;
|
|
166
|
+
let state: TraceState = 'clean';
|
|
167
|
+
let value = this.labels.cleanEvent;
|
|
168
|
+
if (this.metrics.jitter > 58 && this.metrics.snapping > 58) {
|
|
169
|
+
state = 'jitter';
|
|
170
|
+
value = this.labels.combinedEvent;
|
|
171
|
+
} else if (this.metrics.jitter > 58) {
|
|
172
|
+
state = 'jitter';
|
|
173
|
+
value = `${this.labels.jitterEvent} ${this.metrics.deviation}${this.labels.pxUnit}`;
|
|
174
|
+
} else if (this.metrics.snapping > 70) {
|
|
175
|
+
state = 'snapping';
|
|
176
|
+
value = `${this.labels.snappingEvent} ${this.metrics.snapping}${this.labels.percentUnit}`;
|
|
177
|
+
}
|
|
178
|
+
this.events.unshift({ state, value });
|
|
179
|
+
this.events.length = Math.min(this.events.length, MAX_LOG);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private render() {
|
|
183
|
+
if (this.dirty) this.draw();
|
|
184
|
+
this.raf = window.requestAnimationFrame(() => this.render());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private setText(element: HTMLElement | null, value: string) {
|
|
188
|
+
if (element) element.textContent = value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private renderPanel() {
|
|
192
|
+
this.setText(this.elements.samples, String(this.points.length));
|
|
193
|
+
this.setText(this.elements.jitterScore, `${this.metrics.jitter}${this.labels.percentUnit}`);
|
|
194
|
+
this.setText(this.elements.snappingScore, `${this.metrics.snapping}${this.labels.percentUnit}`);
|
|
195
|
+
this.setText(this.elements.straightness, `${this.metrics.straightness}${this.labels.percentUnit}`);
|
|
196
|
+
this.setText(this.elements.deviation, `${this.metrics.deviation}${this.labels.pxUnit}`);
|
|
197
|
+
this.setText(this.elements.sensitivityValue, String(this.sensitivity));
|
|
198
|
+
this.renderStatus();
|
|
199
|
+
this.renderLog();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private renderStatus() {
|
|
203
|
+
const status = this.elements.status;
|
|
204
|
+
if (!status) return;
|
|
205
|
+
let text = this.labels.statusIdle;
|
|
206
|
+
let state = 'idle';
|
|
207
|
+
if (this.points.length > 8) {
|
|
208
|
+
const hasJitter = this.metrics.jitter > 58;
|
|
209
|
+
const hasSnapping = this.metrics.snapping > 70;
|
|
210
|
+
if (hasJitter && hasSnapping) {
|
|
211
|
+
text = this.labels.statusMixed;
|
|
212
|
+
state = 'mixed';
|
|
213
|
+
} else if (hasJitter) {
|
|
214
|
+
text = this.labels.statusJitter;
|
|
215
|
+
state = 'jitter';
|
|
216
|
+
} else if (hasSnapping) {
|
|
217
|
+
text = this.labels.statusSnapping;
|
|
218
|
+
state = 'snapping';
|
|
219
|
+
} else {
|
|
220
|
+
text = this.labels.statusHealthy;
|
|
221
|
+
state = 'healthy';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
status.textContent = text;
|
|
225
|
+
status.dataset.state = state;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private renderLog() {
|
|
229
|
+
const log = this.elements.log;
|
|
230
|
+
if (!log) return;
|
|
231
|
+
if (this.events.length === 0) {
|
|
232
|
+
log.innerHTML = `<li class="mjas-empty">${this.labels.emptyLog}</li>`;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
log.innerHTML = this.events.map((event) => `<li class="${event.state}"><span>${event.value}</span></li>`).join('');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private reset() {
|
|
239
|
+
this.points.length = 0;
|
|
240
|
+
this.events.length = 0;
|
|
241
|
+
this.metrics = { jitter: 0, snapping: 0, straightness: 0, deviation: 0 };
|
|
242
|
+
this.dirty = true;
|
|
243
|
+
this.renderPanel();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type TraceState = 'clean' | 'jitter' | 'snapping';
|
|
2
|
+
|
|
3
|
+
export interface PointSample { x: number; y: number; t: number; fresh?: boolean; }
|
|
4
|
+
export interface TraceEvent { state: TraceState; value: string; }
|
|
5
|
+
|
|
6
|
+
export interface Elements {
|
|
7
|
+
stage: HTMLElement | null;
|
|
8
|
+
canvas: HTMLCanvasElement | null;
|
|
9
|
+
samples: HTMLElement | null;
|
|
10
|
+
jitterScore: HTMLElement | null;
|
|
11
|
+
snappingScore: HTMLElement | null;
|
|
12
|
+
straightness: HTMLElement | null;
|
|
13
|
+
deviation: HTMLElement | null;
|
|
14
|
+
status: HTMLElement | null;
|
|
15
|
+
sensitivity: HTMLInputElement | null;
|
|
16
|
+
sensitivityValue: HTMLElement | null;
|
|
17
|
+
reset: HTMLElement | null;
|
|
18
|
+
log: HTMLElement | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Labels {
|
|
22
|
+
statusIdle: string;
|
|
23
|
+
statusHealthy: string;
|
|
24
|
+
statusJitter: string;
|
|
25
|
+
statusSnapping: string;
|
|
26
|
+
statusMixed: string;
|
|
27
|
+
emptyLog: string;
|
|
28
|
+
jitterEvent: string;
|
|
29
|
+
snappingEvent: string;
|
|
30
|
+
combinedEvent: string;
|
|
31
|
+
cleanEvent: string;
|
|
32
|
+
pxUnit: string;
|
|
33
|
+
percentUnit: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const MAX_POINTS = 2400;
|
|
37
|
+
export const MAX_LOG = 9;
|
|
38
|
+
export const WINDOW_SIZE = 32;
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
.mjas-root {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
width: 100%;
|
|
4
|
+
max-width: 100%;
|
|
5
|
+
min-width: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.mjas-root *,
|
|
9
|
+
.mjas-root *::before,
|
|
10
|
+
.mjas-root *::after {
|
|
11
|
+
box-sizing: inherit;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.mjas-shell {
|
|
15
|
+
display: grid;
|
|
16
|
+
grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr);
|
|
17
|
+
gap: 1rem;
|
|
18
|
+
width: 100%;
|
|
19
|
+
min-width: 0;
|
|
20
|
+
padding: clamp(0.8rem, 2.5vw, 1.1rem);
|
|
21
|
+
border: 1px solid rgba(12, 74, 110, 0.2);
|
|
22
|
+
border-radius: 8px;
|
|
23
|
+
background:
|
|
24
|
+
linear-gradient(135deg, rgba(241, 245, 249, 0.98), rgba(224, 242, 254, 0.9)),
|
|
25
|
+
repeating-linear-gradient(135deg, rgba(14, 116, 144, 0.07) 0 1px, transparent 1px 18px);
|
|
26
|
+
color: #0f172a;
|
|
27
|
+
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.13);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.theme-dark .mjas-shell {
|
|
31
|
+
border-color: rgba(125, 211, 252, 0.25);
|
|
32
|
+
background:
|
|
33
|
+
linear-gradient(135deg, rgba(2, 6, 23, 0.96), rgba(12, 74, 110, 0.76)),
|
|
34
|
+
repeating-linear-gradient(135deg, rgba(103, 232, 249, 0.08) 0 1px, transparent 1px 18px);
|
|
35
|
+
color: #e0f2fe;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.mjas-stage {
|
|
39
|
+
--mjas-canvas-bg: #f8fafc;
|
|
40
|
+
--mjas-grid: rgba(14, 116, 144, 0.16);
|
|
41
|
+
--mjas-line: #0e7490;
|
|
42
|
+
--mjas-node: #ca8a04;
|
|
43
|
+
--mjas-hot-node: #e11d48;
|
|
44
|
+
|
|
45
|
+
position: relative;
|
|
46
|
+
min-height: 520px;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
border: 1px solid rgba(14, 116, 144, 0.24);
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
background: #f8fafc;
|
|
51
|
+
cursor: crosshair;
|
|
52
|
+
touch-action: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.theme-dark .mjas-stage {
|
|
56
|
+
--mjas-canvas-bg: #030712;
|
|
57
|
+
--mjas-grid: rgba(186, 230, 253, 0.12);
|
|
58
|
+
--mjas-line: #67e8f9;
|
|
59
|
+
--mjas-node: #facc15;
|
|
60
|
+
--mjas-hot-node: #f43f5e;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.mjas-canvas {
|
|
64
|
+
display: block;
|
|
65
|
+
width: 100%;
|
|
66
|
+
height: 100%;
|
|
67
|
+
min-height: 520px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.mjas-canvas-copy {
|
|
71
|
+
position: absolute;
|
|
72
|
+
inset: auto 1rem 1rem;
|
|
73
|
+
display: grid;
|
|
74
|
+
gap: 0.35rem;
|
|
75
|
+
max-width: 34rem;
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.mjas-canvas-copy span {
|
|
80
|
+
display: inline-flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 0.4rem;
|
|
83
|
+
width: fit-content;
|
|
84
|
+
padding: 0.35rem 0.55rem;
|
|
85
|
+
border: 1px solid rgba(14, 116, 144, 0.22);
|
|
86
|
+
border-radius: 999px;
|
|
87
|
+
background: rgba(255, 255, 255, 0.86);
|
|
88
|
+
color: #0f5f76;
|
|
89
|
+
font-size: 0.78rem;
|
|
90
|
+
font-weight: 900;
|
|
91
|
+
text-transform: uppercase;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.mjas-canvas-copy svg {
|
|
95
|
+
width: 1rem;
|
|
96
|
+
height: 1rem;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.mjas-canvas-copy strong {
|
|
100
|
+
color: #0f172a;
|
|
101
|
+
font-size: clamp(1.3rem, 4.5vw, 2.2rem);
|
|
102
|
+
line-height: 1.08;
|
|
103
|
+
text-shadow: 0 2px 18px rgba(255, 255, 255, 0.78);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.mjas-canvas-copy small {
|
|
107
|
+
color: #334155;
|
|
108
|
+
font-size: 0.95rem;
|
|
109
|
+
font-weight: 700;
|
|
110
|
+
line-height: 1.35;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.theme-dark .mjas-canvas-copy span {
|
|
114
|
+
border-color: rgba(125, 211, 252, 0.24);
|
|
115
|
+
background: rgba(8, 47, 73, 0.72);
|
|
116
|
+
color: #bae6fd;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.theme-dark .mjas-canvas-copy strong {
|
|
120
|
+
color: #f8fafc;
|
|
121
|
+
text-shadow: 0 2px 18px rgba(0, 0, 0, 0.7);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.theme-dark .mjas-canvas-copy small {
|
|
125
|
+
color: #cbd5e1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.mjas-console {
|
|
129
|
+
display: grid;
|
|
130
|
+
gap: 0.75rem;
|
|
131
|
+
align-content: start;
|
|
132
|
+
min-width: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.mjas-status,
|
|
136
|
+
.mjas-score-grid > div,
|
|
137
|
+
.mjas-readouts > div,
|
|
138
|
+
.mjas-sensitivity,
|
|
139
|
+
.mjas-log {
|
|
140
|
+
border: 1px solid rgba(14, 116, 144, 0.18);
|
|
141
|
+
border-radius: 8px;
|
|
142
|
+
background: rgba(255, 255, 255, 0.75);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.theme-dark .mjas-status,
|
|
146
|
+
.theme-dark .mjas-score-grid > div,
|
|
147
|
+
.theme-dark .mjas-readouts > div,
|
|
148
|
+
.theme-dark .mjas-sensitivity,
|
|
149
|
+
.theme-dark .mjas-log {
|
|
150
|
+
border-color: rgba(125, 211, 252, 0.16);
|
|
151
|
+
background: rgba(15, 23, 42, 0.58);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.mjas-status {
|
|
155
|
+
display: grid;
|
|
156
|
+
grid-template-rows: auto minmax(4.4rem, auto) minmax(2.6rem, auto);
|
|
157
|
+
gap: 0.2rem;
|
|
158
|
+
padding: 1rem;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.mjas-status span,
|
|
162
|
+
.mjas-score-grid span,
|
|
163
|
+
.mjas-readouts span,
|
|
164
|
+
.mjas-sensitivity span,
|
|
165
|
+
.mjas-log-head {
|
|
166
|
+
color: #475569;
|
|
167
|
+
font-size: 0.76rem;
|
|
168
|
+
font-weight: 900;
|
|
169
|
+
text-transform: uppercase;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.theme-dark .mjas-status span,
|
|
173
|
+
.theme-dark .mjas-score-grid span,
|
|
174
|
+
.theme-dark .mjas-readouts span,
|
|
175
|
+
.theme-dark .mjas-sensitivity span,
|
|
176
|
+
.theme-dark .mjas-log-head {
|
|
177
|
+
color: #bae6fd;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.mjas-status strong {
|
|
181
|
+
font-size: clamp(2.6rem, 9vw, 4.6rem);
|
|
182
|
+
line-height: 0.95;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.mjas-status small {
|
|
186
|
+
display: block;
|
|
187
|
+
min-height: 2.6rem;
|
|
188
|
+
color: #334155;
|
|
189
|
+
font-weight: 800;
|
|
190
|
+
line-height: 1.3;
|
|
191
|
+
overflow-wrap: anywhere;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.mjas-status small[data-state="healthy"] {
|
|
195
|
+
color: #047857;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.mjas-status small[data-state="jitter"],
|
|
199
|
+
.mjas-status small[data-state="mixed"] {
|
|
200
|
+
color: #be123c;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.mjas-status small[data-state="snapping"] {
|
|
204
|
+
color: #b45309;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.theme-dark .mjas-status small {
|
|
208
|
+
color: #dbeafe;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.theme-dark .mjas-status small[data-state="healthy"] {
|
|
212
|
+
color: #86efac;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.theme-dark .mjas-status small[data-state="jitter"],
|
|
216
|
+
.theme-dark .mjas-status small[data-state="mixed"] {
|
|
217
|
+
color: #fda4af;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.theme-dark .mjas-status small[data-state="snapping"] {
|
|
221
|
+
color: #fde68a;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.mjas-score-grid,
|
|
225
|
+
.mjas-readouts,
|
|
226
|
+
.mjas-actions {
|
|
227
|
+
display: grid;
|
|
228
|
+
gap: 0.65rem;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.mjas-score-grid,
|
|
232
|
+
.mjas-readouts {
|
|
233
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.mjas-score-grid > div,
|
|
237
|
+
.mjas-readouts > div {
|
|
238
|
+
display: grid;
|
|
239
|
+
grid-template-rows: minmax(1.9rem, auto) minmax(2.35rem, auto);
|
|
240
|
+
gap: 0.2rem;
|
|
241
|
+
min-width: 0;
|
|
242
|
+
padding: 0.85rem;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.mjas-score-grid strong,
|
|
246
|
+
.mjas-readouts strong {
|
|
247
|
+
display: flex;
|
|
248
|
+
align-items: end;
|
|
249
|
+
min-height: 2.35rem;
|
|
250
|
+
overflow-wrap: anywhere;
|
|
251
|
+
font-size: clamp(1.35rem, 5vw, 2rem);
|
|
252
|
+
line-height: 1.05;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.mjas-sensitivity {
|
|
256
|
+
display: grid;
|
|
257
|
+
grid-template-rows: minmax(1.2rem, auto) minmax(1.5rem, auto) auto;
|
|
258
|
+
gap: 0.55rem;
|
|
259
|
+
padding: 0.85rem;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.mjas-sensitivity b {
|
|
263
|
+
display: grid;
|
|
264
|
+
grid-template-columns: auto 1fr auto;
|
|
265
|
+
gap: 0.5rem;
|
|
266
|
+
align-items: center;
|
|
267
|
+
color: #0369a1;
|
|
268
|
+
font-style: normal;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.mjas-sensitivity em {
|
|
272
|
+
color: #64748b;
|
|
273
|
+
font-style: normal;
|
|
274
|
+
font-size: 0.78rem;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.theme-dark .mjas-sensitivity b {
|
|
278
|
+
color: #67e8f9;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.theme-dark .mjas-sensitivity em {
|
|
282
|
+
color: #cbd5e1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.mjas-sensitivity input {
|
|
286
|
+
width: 100%;
|
|
287
|
+
accent-color: #0ea5e9;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.mjas-actions button {
|
|
291
|
+
display: inline-flex;
|
|
292
|
+
align-items: center;
|
|
293
|
+
justify-content: center;
|
|
294
|
+
gap: 0.4rem;
|
|
295
|
+
min-width: 0;
|
|
296
|
+
border: 0;
|
|
297
|
+
border-radius: 8px;
|
|
298
|
+
background: #0f172a;
|
|
299
|
+
color: #fff;
|
|
300
|
+
cursor: pointer;
|
|
301
|
+
font: inherit;
|
|
302
|
+
font-weight: 900;
|
|
303
|
+
padding: 0.7rem 0.65rem;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.mjas-actions svg {
|
|
307
|
+
flex: 0 0 auto;
|
|
308
|
+
width: 1rem;
|
|
309
|
+
height: 1rem;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.theme-dark .mjas-actions button {
|
|
313
|
+
background: #e0f2fe;
|
|
314
|
+
color: #082f49;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.mjas-shift {
|
|
318
|
+
margin: 0;
|
|
319
|
+
min-height: 4.35rem;
|
|
320
|
+
padding: 0.75rem 0.85rem;
|
|
321
|
+
border: 1px solid rgba(202, 138, 4, 0.24);
|
|
322
|
+
border-radius: 8px;
|
|
323
|
+
background: rgba(254, 249, 195, 0.82);
|
|
324
|
+
color: #713f12;
|
|
325
|
+
font-size: 0.9rem;
|
|
326
|
+
font-weight: 750;
|
|
327
|
+
line-height: 1.35;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.theme-dark .mjas-shift {
|
|
331
|
+
border-color: rgba(250, 204, 21, 0.22);
|
|
332
|
+
background: rgba(113, 63, 18, 0.4);
|
|
333
|
+
color: #fef3c7;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.mjas-log {
|
|
337
|
+
display: grid;
|
|
338
|
+
grid-auto-rows: minmax(2.2rem, auto);
|
|
339
|
+
gap: 0.45rem;
|
|
340
|
+
min-height: 8.8rem;
|
|
341
|
+
height: 13rem;
|
|
342
|
+
max-height: 13rem;
|
|
343
|
+
overflow: auto;
|
|
344
|
+
margin: 0;
|
|
345
|
+
padding: 0.75rem;
|
|
346
|
+
list-style: none;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.mjas-log li,
|
|
350
|
+
.mjas-empty {
|
|
351
|
+
padding: 0.55rem 0.65rem;
|
|
352
|
+
border-radius: 8px;
|
|
353
|
+
background: rgba(224, 242, 254, 0.78);
|
|
354
|
+
color: #0f172a;
|
|
355
|
+
font-size: 0.86rem;
|
|
356
|
+
font-weight: 800;
|
|
357
|
+
overflow-wrap: anywhere;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.mjas-log li.jitter {
|
|
361
|
+
background: rgba(255, 228, 230, 0.9);
|
|
362
|
+
color: #9f1239;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.mjas-log li.snapping {
|
|
366
|
+
background: rgba(254, 243, 199, 0.9);
|
|
367
|
+
color: #92400e;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.theme-dark .mjas-log li,
|
|
371
|
+
.theme-dark .mjas-empty {
|
|
372
|
+
background: rgba(8, 47, 73, 0.72);
|
|
373
|
+
color: #dbeafe;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.theme-dark .mjas-log li.jitter {
|
|
377
|
+
background: rgba(136, 19, 55, 0.58);
|
|
378
|
+
color: #ffe4e6;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.theme-dark .mjas-log li.snapping {
|
|
382
|
+
background: rgba(120, 53, 15, 0.58);
|
|
383
|
+
color: #fef3c7;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
@media (max-width: 880px) {
|
|
387
|
+
.mjas-shell {
|
|
388
|
+
grid-template-columns: 1fr;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.mjas-stage,
|
|
392
|
+
.mjas-canvas {
|
|
393
|
+
min-height: 430px;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@media (max-width: 520px) {
|
|
398
|
+
.mjas-shell {
|
|
399
|
+
padding: 0.7rem;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.mjas-score-grid,
|
|
403
|
+
.mjas-readouts,
|
|
404
|
+
.mjas-actions {
|
|
405
|
+
grid-template-columns: 1fr;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.mjas-stage,
|
|
409
|
+
.mjas-canvas {
|
|
410
|
+
min-height: 360px;
|
|
411
|
+
}
|
|
412
|
+
}
|