@jspsych/plugin-tobii-validation 0.1.1

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/src/types.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Type definitions for validation plugin
3
+ */
4
+ export interface ValidationPoint {
5
+ x: number;
6
+ y: number;
7
+ }
8
+ export interface ValidationParameters {
9
+ /** Number of validation points */
10
+ validation_points?: 5 | 9 | 13 | 15 | 19 | 25;
11
+ /** Size of validation point in pixels */
12
+ point_size?: number;
13
+ /** Color of validation point */
14
+ point_color?: string;
15
+ /** Duration to collect data at each point (ms) */
16
+ collection_duration?: number;
17
+ /** Show progress indicator */
18
+ show_progress?: boolean;
19
+ /** Custom validation points (overrides validation_points) */
20
+ custom_points?: ValidationPoint[];
21
+ /** Show visual feedback after validation */
22
+ show_feedback?: boolean;
23
+ /** Instructions text */
24
+ instructions?: string;
25
+ /** Background color of the validation container */
26
+ background_color?: string;
27
+ /** Primary button color */
28
+ button_color?: string;
29
+ /** Primary button hover color */
30
+ button_hover_color?: string;
31
+ /** Retry button color */
32
+ retry_button_color?: string;
33
+ /** Retry button hover color */
34
+ retry_button_hover_color?: string;
35
+ /** Success message color */
36
+ success_color?: string;
37
+ /** Error message color */
38
+ error_color?: string;
39
+ /** Normalized tolerance for acceptable accuracy (0-1 scale) */
40
+ tolerance?: number;
41
+ /** Maximum number of retry attempts allowed on validation failure */
42
+ max_retries?: number;
43
+ /** Duration of zoom in/out animations in ms */
44
+ zoom_duration?: number;
45
+ }
46
+ export interface GazeSample {
47
+ x: number;
48
+ y: number;
49
+ }
50
+ export interface PointValidationData {
51
+ point: ValidationPoint;
52
+ accuracyNorm: number;
53
+ precisionNorm: number;
54
+ meanGaze?: {
55
+ x: number;
56
+ y: number;
57
+ };
58
+ numSamples?: number;
59
+ numSamplesTotal?: number;
60
+ numSamplesSkipped?: number;
61
+ gazeSamples?: GazeSample[];
62
+ }
63
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,oBAAoB;IACnC,kCAAkC;IAClC,iBAAiB,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC9C,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,8BAA8B;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,eAAe,EAAE,CAAC;IAClC,4CAA4C;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wBAAwB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,+BAA+B;IAC/B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,4BAA4B;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B"}
package/src/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Type definitions for validation plugin
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/src/types.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Type definitions for validation plugin
3
+ */
4
+
5
+ export interface ValidationPoint {
6
+ x: number;
7
+ y: number;
8
+ }
9
+
10
+ export interface ValidationParameters {
11
+ /** Number of validation points */
12
+ validation_points?: 5 | 9 | 13 | 15 | 19 | 25;
13
+ /** Size of validation point in pixels */
14
+ point_size?: number;
15
+ /** Color of validation point */
16
+ point_color?: string;
17
+ /** Duration to collect data at each point (ms) */
18
+ collection_duration?: number;
19
+ /** Show progress indicator */
20
+ show_progress?: boolean;
21
+ /** Custom validation points (overrides validation_points) */
22
+ custom_points?: ValidationPoint[];
23
+ /** Show visual feedback after validation */
24
+ show_feedback?: boolean;
25
+ /** Instructions text */
26
+ instructions?: string;
27
+ /** Background color of the validation container */
28
+ background_color?: string;
29
+ /** Primary button color */
30
+ button_color?: string;
31
+ /** Primary button hover color */
32
+ button_hover_color?: string;
33
+ /** Retry button color */
34
+ retry_button_color?: string;
35
+ /** Retry button hover color */
36
+ retry_button_hover_color?: string;
37
+ /** Success message color */
38
+ success_color?: string;
39
+ /** Error message color */
40
+ error_color?: string;
41
+ /** Normalized tolerance for acceptable accuracy (0-1 scale) */
42
+ tolerance?: number;
43
+ /** Maximum number of retry attempts allowed on validation failure */
44
+ max_retries?: number;
45
+ /** Duration of zoom in/out animations in ms */
46
+ zoom_duration?: number;
47
+ }
48
+
49
+ export interface GazeSample {
50
+ x: number;
51
+ y: number;
52
+ }
53
+
54
+ export interface PointValidationData {
55
+ point: ValidationPoint;
56
+ accuracyNorm: number;
57
+ precisionNorm: number;
58
+ meanGaze?: { x: number; y: number };
59
+ numSamples?: number;
60
+ numSamplesTotal?: number;
61
+ numSamplesSkipped?: number;
62
+ gazeSamples?: GazeSample[];
63
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Validation display component
3
+ */
4
+ import type { ValidationPoint, ValidationParameters, PointValidationData } from './types';
5
+ export declare class ValidationDisplay {
6
+ private displayElement;
7
+ private params;
8
+ private container;
9
+ private currentPoint;
10
+ private progressElement;
11
+ private currentX;
12
+ private currentY;
13
+ constructor(displayElement: HTMLElement, params: ValidationParameters);
14
+ private createContainer;
15
+ private createProgressIndicator;
16
+ showInstructions(): Promise<void>;
17
+ /**
18
+ * Initialize the traveling point at screen center
19
+ */
20
+ initializePoint(): Promise<void>;
21
+ /**
22
+ * Travel to the next point location with smooth animation
23
+ */
24
+ travelToPoint(point: ValidationPoint, index: number, total: number): Promise<void>;
25
+ /**
26
+ * Play zoom out animation (point grows larger)
27
+ */
28
+ playZoomOut(): Promise<void>;
29
+ /**
30
+ * Play zoom in animation (point shrinks to fixation size)
31
+ */
32
+ playZoomIn(): Promise<void>;
33
+ /**
34
+ * Reset point state after data collection (keeps element for continued travel)
35
+ */
36
+ resetPointForTravel(): Promise<void>;
37
+ hidePoint(): Promise<void>;
38
+ /**
39
+ * Show validation result
40
+ * @param success Whether validation passed
41
+ * @param averageAccuracyNorm Average accuracy in normalized units
42
+ * @param averagePrecisionNorm Average precision in normalized units
43
+ * @param pointData Per-point validation data
44
+ * @param tolerance Tolerance threshold
45
+ * @param canRetry Whether a retry button should be shown on failure
46
+ * @returns 'retry' if user chose to retry, 'continue' otherwise
47
+ */
48
+ showResult(success: boolean, averageAccuracyNorm?: number, _averagePrecisionNorm?: number, pointData?: PointValidationData[], tolerance?: number, canRetry?: boolean): Promise<'retry' | 'continue'>;
49
+ /**
50
+ * Reset display state for a retry attempt
51
+ */
52
+ resetForRetry(): void;
53
+ private createVisualFeedback;
54
+ clear(): void;
55
+ private delay;
56
+ }
57
+ //# sourceMappingURL=validation-display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-display.d.ts","sourceRoot":"","sources":["validation-display.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE1F,qBAAa,iBAAiB;IAQ1B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,MAAM;IARhB,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAe;gBAGrB,cAAc,EAAE,WAAW,EAC3B,MAAM,EAAE,oBAAoB;IAWtC,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,uBAAuB;IAQzB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCvC;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BtC;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4CxF;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAUlC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAUpC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAQhC;;;;;;;;;OASG;IACG,UAAU,CACd,OAAO,EAAE,OAAO,EAChB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,qBAAqB,CAAC,EAAE,MAAM,EAC9B,SAAS,CAAC,EAAE,mBAAmB,EAAE,EACjC,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,GAAE,OAAe,GACxB,OAAO,CAAC,OAAO,GAAG,UAAU,CAAC;IAmDhC;;OAEG;IACH,aAAa,IAAI,IAAI;IAOrB,OAAO,CAAC,oBAAoB;IAiG5B,KAAK,IAAI,IAAI;IAOb,OAAO,CAAC,KAAK;CAGd"}
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Validation display component
3
+ */
4
+ export class ValidationDisplay {
5
+ constructor(displayElement, params) {
6
+ this.displayElement = displayElement;
7
+ this.params = params;
8
+ this.currentPoint = null;
9
+ this.progressElement = null;
10
+ this.currentX = 0.5; // Start at center
11
+ this.currentY = 0.5;
12
+ this.container = this.createContainer();
13
+ this.displayElement.appendChild(this.container);
14
+ if (params.show_progress) {
15
+ this.progressElement = this.createProgressIndicator();
16
+ this.displayElement.appendChild(this.progressElement);
17
+ }
18
+ }
19
+ createContainer() {
20
+ const container = document.createElement('div');
21
+ container.className = 'tobii-validation-container';
22
+ return container;
23
+ }
24
+ createProgressIndicator() {
25
+ const progress = document.createElement('div');
26
+ progress.className = 'tobii-validation-progress';
27
+ progress.setAttribute('role', 'status');
28
+ progress.setAttribute('aria-live', 'polite');
29
+ return progress;
30
+ }
31
+ async showInstructions() {
32
+ const wrapper = document.createElement('div');
33
+ wrapper.className = 'tobii-validation-instructions';
34
+ wrapper.setAttribute('role', 'dialog');
35
+ wrapper.setAttribute('aria-label', 'Eye tracker validation instructions');
36
+ const content = document.createElement('div');
37
+ content.className = 'instructions-content';
38
+ const heading = document.createElement('h2');
39
+ heading.textContent = 'Eye Tracker Validation';
40
+ content.appendChild(heading);
41
+ const paragraph = document.createElement('p');
42
+ paragraph.innerHTML =
43
+ this.params.instructions || 'Look at each point to validate calibration accuracy.';
44
+ content.appendChild(paragraph);
45
+ const button = document.createElement('button');
46
+ button.className = 'validation-start-btn';
47
+ button.textContent = 'Start Validation';
48
+ content.appendChild(button);
49
+ wrapper.appendChild(content);
50
+ this.container.appendChild(wrapper);
51
+ return new Promise((resolve) => {
52
+ button.addEventListener('click', () => {
53
+ wrapper.remove();
54
+ resolve();
55
+ });
56
+ });
57
+ }
58
+ /**
59
+ * Initialize the traveling point at screen center
60
+ */
61
+ async initializePoint() {
62
+ if (this.currentPoint)
63
+ return;
64
+ this.currentPoint = document.createElement('div');
65
+ this.currentPoint.className = 'tobii-validation-point';
66
+ this.currentPoint.setAttribute('role', 'img');
67
+ this.currentPoint.setAttribute('aria-label', 'Validation target point');
68
+ // Start at center
69
+ const x = 0.5 * window.innerWidth;
70
+ const y = 0.5 * window.innerHeight;
71
+ this.currentX = 0.5;
72
+ this.currentY = 0.5;
73
+ Object.assign(this.currentPoint.style, {
74
+ left: `${x}px`,
75
+ top: `${y}px`,
76
+ width: `${this.params.point_size || 20}px`,
77
+ height: `${this.params.point_size || 20}px`,
78
+ backgroundColor: this.params.point_color || '#00ff00',
79
+ transition: 'none',
80
+ });
81
+ this.container.appendChild(this.currentPoint);
82
+ // Brief pause to show point at center before traveling
83
+ await this.delay(this.params.zoom_duration || 300);
84
+ }
85
+ /**
86
+ * Travel to the next point location with smooth animation
87
+ */
88
+ async travelToPoint(point, index, total) {
89
+ if (!this.currentPoint) {
90
+ await this.initializePoint();
91
+ }
92
+ // Update progress
93
+ if (this.progressElement) {
94
+ this.progressElement.textContent = `Point ${index + 1} of ${total}`;
95
+ }
96
+ // Update aria-label with current point number
97
+ this.currentPoint.setAttribute('aria-label', `Validation target point ${index + 1} of ${total}`);
98
+ // Calculate travel distance for dynamic duration
99
+ const dx = point.x - this.currentX;
100
+ const dy = point.y - this.currentY;
101
+ const distance = Math.sqrt(dx * dx + dy * dy);
102
+ // Travel duration: 150ms base + 200ms per normalized unit distance (quick travel)
103
+ const travelDuration = Math.max(150, Math.min(400, 150 + distance * 200));
104
+ // Convert normalized coordinates to pixels
105
+ const x = point.x * window.innerWidth;
106
+ const y = point.y * window.innerHeight;
107
+ // Set up travel transition
108
+ this.currentPoint.style.transition = `left ${travelDuration}ms ease-in-out, top ${travelDuration}ms ease-in-out`;
109
+ this.currentPoint.classList.remove('animation-zoom-out', 'animation-zoom-in');
110
+ // Move to new position
111
+ this.currentPoint.style.left = `${x}px`;
112
+ this.currentPoint.style.top = `${y}px`;
113
+ // Update current position
114
+ this.currentX = point.x;
115
+ this.currentY = point.y;
116
+ // Wait for travel to complete
117
+ await this.delay(travelDuration);
118
+ }
119
+ /**
120
+ * Play zoom out animation (point grows larger)
121
+ */
122
+ async playZoomOut() {
123
+ if (!this.currentPoint)
124
+ return;
125
+ this.currentPoint.style.transition = 'none';
126
+ this.currentPoint.classList.remove('animation-zoom-in');
127
+ this.currentPoint.classList.add('animation-zoom-out');
128
+ await this.delay(this.params.zoom_duration || 300);
129
+ }
130
+ /**
131
+ * Play zoom in animation (point shrinks to fixation size)
132
+ */
133
+ async playZoomIn() {
134
+ if (!this.currentPoint)
135
+ return;
136
+ this.currentPoint.classList.remove('animation-zoom-out');
137
+ this.currentPoint.classList.add('animation-zoom-in');
138
+ await this.delay(this.params.zoom_duration || 300);
139
+ }
140
+ /**
141
+ * Reset point state after data collection (keeps element for continued travel)
142
+ */
143
+ async resetPointForTravel() {
144
+ if (!this.currentPoint)
145
+ return;
146
+ // Reset to normal size for next travel
147
+ this.currentPoint.classList.remove('animation-zoom-out', 'animation-zoom-in');
148
+ this.currentPoint.style.transform = 'translate(-50%, -50%) scale(1)';
149
+ await this.delay(50);
150
+ }
151
+ async hidePoint() {
152
+ if (this.currentPoint) {
153
+ this.currentPoint.remove();
154
+ this.currentPoint = null;
155
+ }
156
+ await this.delay(200);
157
+ }
158
+ /**
159
+ * Show validation result
160
+ * @param success Whether validation passed
161
+ * @param averageAccuracyNorm Average accuracy in normalized units
162
+ * @param averagePrecisionNorm Average precision in normalized units
163
+ * @param pointData Per-point validation data
164
+ * @param tolerance Tolerance threshold
165
+ * @param canRetry Whether a retry button should be shown on failure
166
+ * @returns 'retry' if user chose to retry, 'continue' otherwise
167
+ */
168
+ async showResult(success, averageAccuracyNorm, _averagePrecisionNorm, pointData, tolerance, canRetry = false) {
169
+ const result = document.createElement('div');
170
+ result.className = 'tobii-validation-result';
171
+ result.setAttribute('role', 'alert');
172
+ result.setAttribute('aria-live', 'assertive');
173
+ let feedbackHTML = '';
174
+ if (this.params.show_feedback && pointData) {
175
+ feedbackHTML = this.createVisualFeedback(pointData, tolerance);
176
+ }
177
+ const statusClass = success ? 'success' : 'error';
178
+ const statusText = success ? 'Validation Passed' : 'Validation Failed';
179
+ let buttonsHTML;
180
+ if (success) {
181
+ buttonsHTML = `<button class="validation-continue-btn">Continue</button>`;
182
+ }
183
+ else if (canRetry) {
184
+ buttonsHTML = `<button class="validation-retry-btn">Retry</button>
185
+ <button class="validation-continue-btn" style="margin-left: 10px;">Continue</button>`;
186
+ }
187
+ else {
188
+ buttonsHTML = `<button class="validation-continue-btn">Continue</button>`;
189
+ }
190
+ result.innerHTML = `
191
+ <div class="tobii-validation-result-content ${statusClass}">
192
+ <h2>${statusText}</h2>
193
+ <p>Average error: ${((averageAccuracyNorm || 0) * 100).toFixed(1)}% (tolerance: ${((tolerance || 0) * 100).toFixed(0)}%)</p>
194
+ ${feedbackHTML}
195
+ ${buttonsHTML}
196
+ </div>
197
+ `;
198
+ this.container.appendChild(result);
199
+ return new Promise((resolve) => {
200
+ const retryBtn = result.querySelector('.validation-retry-btn');
201
+ const continueBtn = result.querySelector('.validation-continue-btn');
202
+ retryBtn?.addEventListener('click', () => {
203
+ result.remove();
204
+ resolve('retry');
205
+ });
206
+ continueBtn?.addEventListener('click', () => {
207
+ result.remove();
208
+ resolve('continue');
209
+ });
210
+ });
211
+ }
212
+ /**
213
+ * Reset display state for a retry attempt
214
+ */
215
+ resetForRetry() {
216
+ this.container.innerHTML = '';
217
+ this.currentPoint = null;
218
+ this.currentX = 0.5;
219
+ this.currentY = 0.5;
220
+ }
221
+ createVisualFeedback(pointData, tolerance) {
222
+ const tol = tolerance || 0.05;
223
+ // Create full-screen visualization showing actual screen positions
224
+ const targetMarkers = pointData
225
+ .map((data, idx) => {
226
+ const x = data.point.x * 100;
227
+ const y = data.point.y * 100;
228
+ return `
229
+ <div class="feedback-target" style="
230
+ left: ${x}%;
231
+ top: ${y}%;
232
+ " title="Target ${idx + 1}">
233
+ <span class="target-label">${idx + 1}</span>
234
+ </div>
235
+ `;
236
+ })
237
+ .join('');
238
+ // Mean gaze positions - color coded based on tolerance with non-color indicators
239
+ const gazeMarkers = pointData
240
+ .map((data, idx) => {
241
+ if (!data.meanGaze)
242
+ return '';
243
+ const x = data.meanGaze.x * 100;
244
+ const y = data.meanGaze.y * 100;
245
+ const withinTolerance = data.accuracyNorm <= tol;
246
+ const colorClass = withinTolerance ? 'gaze-pass' : 'gaze-fail';
247
+ const statusSymbol = withinTolerance ? '\u2713' : '\u2717';
248
+ const statusLabel = withinTolerance ? 'pass' : 'fail';
249
+ return `
250
+ <div class="feedback-gaze ${colorClass}" style="
251
+ left: ${x}%;
252
+ top: ${y}%;
253
+ " title="Point ${idx + 1}: ${statusLabel}, error ${(data.accuracyNorm * 100).toFixed(1)}%"
254
+ aria-label="Point ${idx + 1}: ${statusLabel}, error ${(data.accuracyNorm * 100).toFixed(1)}%">
255
+ <span class="gaze-label">${statusSymbol}</span>
256
+ </div>
257
+ `;
258
+ })
259
+ .join('');
260
+ // Draw lines connecting targets to mean gaze positions - color coded
261
+ const connectionLines = pointData
262
+ .map((data) => {
263
+ if (!data.meanGaze)
264
+ return '';
265
+ const x1 = data.point.x * 100;
266
+ const y1 = data.point.y * 100;
267
+ const x2 = data.meanGaze.x * 100;
268
+ const y2 = data.meanGaze.y * 100;
269
+ const withinTolerance = data.accuracyNorm <= tol;
270
+ const lineColor = withinTolerance ? '#4ade80' : '#f87171';
271
+ return `
272
+ <svg class="feedback-line" style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none;">
273
+ <line x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%"
274
+ stroke="${lineColor}" stroke-width="2" stroke-dasharray="5,3" opacity="0.7"/>
275
+ </svg>
276
+ `;
277
+ })
278
+ .join('');
279
+ // Show all individual gaze samples as small dots - color coded per point
280
+ const gazeSampleDots = pointData
281
+ .map((data) => {
282
+ if (!data.gazeSamples)
283
+ return '';
284
+ const withinTolerance = data.accuracyNorm <= tol;
285
+ const sampleClass = withinTolerance ? 'sample-pass' : 'sample-fail';
286
+ return data.gazeSamples
287
+ .map((sample) => {
288
+ const x = sample.x * 100;
289
+ const y = sample.y * 100;
290
+ return `<div class="feedback-sample ${sampleClass}" style="left: ${x}%; top: ${y}%;"></div>`;
291
+ })
292
+ .join('');
293
+ })
294
+ .join('');
295
+ // Calculate aspect ratio from viewport
296
+ const aspectRatio = window.innerWidth / window.innerHeight;
297
+ const canvas = `
298
+ <div class="validation-feedback">
299
+ <div class="feedback-canvas-fullscreen" style="aspect-ratio: ${aspectRatio.toFixed(3)};">
300
+ ${connectionLines}
301
+ ${gazeSampleDots}
302
+ ${targetMarkers}
303
+ ${gazeMarkers}
304
+ </div>
305
+ <div class="feedback-legend">
306
+ <span><span class="legend-color target-legend"></span> Target</span>
307
+ <span><span class="legend-color gaze-pass-legend"></span> Pass (\u2713)</span>
308
+ <span><span class="legend-color gaze-fail-legend"></span> Fail (\u2717)</span>
309
+ </div>
310
+ </div>
311
+ `;
312
+ return canvas;
313
+ }
314
+ clear() {
315
+ this.container.innerHTML = '';
316
+ if (this.progressElement) {
317
+ this.progressElement.textContent = '';
318
+ }
319
+ }
320
+ delay(ms) {
321
+ return new Promise((resolve) => setTimeout(resolve, ms));
322
+ }
323
+ }
324
+ //# sourceMappingURL=validation-display.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-display.js","sourceRoot":"","sources":["validation-display.ts"],"names":[],"mappings":"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"}