@jspsych/plugin-tobii-calibration 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.
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Calibration display component
3
+ */
4
+
5
+ import type { CalibrationPoint, CalibrationParameters } from './types';
6
+
7
+ export class CalibrationDisplay {
8
+ private container: HTMLElement;
9
+ private currentPoint: HTMLElement | null = null;
10
+ private progressElement: HTMLElement | null = null;
11
+ private currentX: number = 0.5; // Start at center
12
+ private currentY: number = 0.5;
13
+
14
+ constructor(
15
+ private displayElement: HTMLElement,
16
+ private params: CalibrationParameters
17
+ ) {
18
+ this.container = this.createContainer();
19
+ this.displayElement.appendChild(this.container);
20
+
21
+ if (params.show_progress) {
22
+ this.progressElement = this.createProgressIndicator();
23
+ this.displayElement.appendChild(this.progressElement);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Create container element
29
+ */
30
+ private createContainer(): HTMLElement {
31
+ const container = document.createElement('div');
32
+ container.className = 'tobii-calibration-container';
33
+ return container;
34
+ }
35
+
36
+ /**
37
+ * Create progress indicator
38
+ */
39
+ private createProgressIndicator(): HTMLElement {
40
+ const progress = document.createElement('div');
41
+ progress.className = 'tobii-calibration-progress';
42
+ progress.setAttribute('role', 'status');
43
+ progress.setAttribute('aria-live', 'polite');
44
+ return progress;
45
+ }
46
+
47
+ /**
48
+ * Show instructions
49
+ */
50
+ async showInstructions(): Promise<void> {
51
+ const wrapper = document.createElement('div');
52
+ wrapper.className = 'tobii-calibration-instructions';
53
+ wrapper.setAttribute('role', 'dialog');
54
+ wrapper.setAttribute('aria-label', 'Eye tracker calibration instructions');
55
+
56
+ const content = document.createElement('div');
57
+ content.className = 'instructions-content';
58
+
59
+ const heading = document.createElement('h2');
60
+ heading.textContent = 'Eye Tracker Calibration';
61
+ content.appendChild(heading);
62
+
63
+ const paragraph = document.createElement('p');
64
+ paragraph.innerHTML =
65
+ this.params.instructions || 'Look at each point and follow the instructions.';
66
+ content.appendChild(paragraph);
67
+
68
+ if (this.params.calibration_mode === 'click') {
69
+ const button = document.createElement('button');
70
+ button.className = 'calibration-start-btn';
71
+ button.textContent = this.params.button_text || 'Start Calibration';
72
+ content.appendChild(button);
73
+ } else {
74
+ const autoMsg = document.createElement('p');
75
+ autoMsg.textContent = 'Starting in a moment...';
76
+ content.appendChild(autoMsg);
77
+ }
78
+
79
+ wrapper.appendChild(content);
80
+ this.container.appendChild(wrapper);
81
+
82
+ if (this.params.calibration_mode === 'click') {
83
+ return new Promise((resolve) => {
84
+ const button = wrapper.querySelector('button');
85
+ button?.addEventListener('click', () => {
86
+ wrapper.remove();
87
+ resolve();
88
+ });
89
+ });
90
+ } else {
91
+ await this.delay(this.params.instruction_display_duration || 3000);
92
+ wrapper.remove();
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Initialize the traveling point at screen center
98
+ */
99
+ async initializePoint(): Promise<void> {
100
+ if (this.currentPoint) return;
101
+
102
+ this.currentPoint = document.createElement('div');
103
+ this.currentPoint.className = 'tobii-calibration-point';
104
+ this.currentPoint.setAttribute('role', 'img');
105
+ this.currentPoint.setAttribute('aria-label', 'Calibration target point');
106
+
107
+ // Start at center
108
+ const x = 0.5 * window.innerWidth;
109
+ const y = 0.5 * window.innerHeight;
110
+ this.currentX = 0.5;
111
+ this.currentY = 0.5;
112
+
113
+ Object.assign(this.currentPoint.style, {
114
+ left: `${x}px`,
115
+ top: `${y}px`,
116
+ width: `${this.params.point_size || 20}px`,
117
+ height: `${this.params.point_size || 20}px`,
118
+ backgroundColor: this.params.point_color || '#ff0000',
119
+ transition: 'none',
120
+ });
121
+
122
+ this.container.appendChild(this.currentPoint);
123
+
124
+ // Brief pause to show point at center before traveling
125
+ await this.delay(this.params.zoom_duration || 300);
126
+ }
127
+
128
+ /**
129
+ * Travel to the next point location with smooth animation
130
+ */
131
+ async travelToPoint(point: CalibrationPoint, index: number, total: number): Promise<void> {
132
+ if (!this.currentPoint) {
133
+ await this.initializePoint();
134
+ }
135
+
136
+ // Update progress
137
+ if (this.progressElement) {
138
+ this.progressElement.textContent = `Point ${index + 1} of ${total}`;
139
+ }
140
+
141
+ // Update aria-label with current point number
142
+ this.currentPoint!.setAttribute(
143
+ 'aria-label',
144
+ `Calibration target point ${index + 1} of ${total}`
145
+ );
146
+
147
+ // Calculate travel distance for dynamic duration
148
+ const dx = point.x - this.currentX;
149
+ const dy = point.y - this.currentY;
150
+ const distance = Math.sqrt(dx * dx + dy * dy);
151
+
152
+ // Travel duration: 150ms base + 200ms per normalized unit distance (quick travel)
153
+ const travelDuration = Math.max(150, Math.min(400, 150 + distance * 200));
154
+
155
+ // Convert normalized coordinates to pixels
156
+ const x = point.x * window.innerWidth;
157
+ const y = point.y * window.innerHeight;
158
+
159
+ // Set up travel transition
160
+ this.currentPoint!.style.transition = `left ${travelDuration}ms ease-in-out, top ${travelDuration}ms ease-in-out`;
161
+ this.currentPoint!.classList.remove(
162
+ 'animation-explosion',
163
+ 'animation-shrink',
164
+ 'animation-pulse',
165
+ 'animation-zoom-out',
166
+ 'animation-zoom-in'
167
+ );
168
+
169
+ // Move to new position
170
+ this.currentPoint!.style.left = `${x}px`;
171
+ this.currentPoint!.style.top = `${y}px`;
172
+
173
+ // Update current position
174
+ this.currentX = point.x;
175
+ this.currentY = point.y;
176
+
177
+ // Wait for travel to complete
178
+ await this.delay(travelDuration);
179
+ }
180
+
181
+ /**
182
+ * Play zoom out animation (point grows larger)
183
+ */
184
+ async playZoomOut(): Promise<void> {
185
+ if (!this.currentPoint) return;
186
+
187
+ this.currentPoint.style.transition = 'none';
188
+ this.currentPoint.classList.remove(
189
+ 'animation-shrink',
190
+ 'animation-pulse',
191
+ 'animation-explosion',
192
+ 'animation-zoom-in'
193
+ );
194
+ this.currentPoint.classList.add('animation-zoom-out');
195
+
196
+ await this.delay(this.params.zoom_duration || 300);
197
+ }
198
+
199
+ /**
200
+ * Play zoom in animation (point shrinks to fixation size)
201
+ */
202
+ async playZoomIn(): Promise<void> {
203
+ if (!this.currentPoint) return;
204
+
205
+ this.currentPoint.classList.remove('animation-zoom-out');
206
+ this.currentPoint.classList.add('animation-zoom-in');
207
+
208
+ await this.delay(this.params.zoom_duration || 300);
209
+ }
210
+
211
+ /**
212
+ * Play explosion animation on the current point
213
+ */
214
+ async playExplosion(success: boolean): Promise<void> {
215
+ if (!this.currentPoint) return;
216
+
217
+ // Remove any existing animation classes
218
+ this.currentPoint.classList.remove('animation-pulse', 'animation-shrink');
219
+
220
+ // Add explosion animation class
221
+ this.currentPoint.classList.add('animation-explosion');
222
+
223
+ // Change color based on success
224
+ if (success) {
225
+ this.currentPoint.style.backgroundColor = '#4ade80'; // green
226
+ } else {
227
+ this.currentPoint.style.backgroundColor = '#f87171'; // red
228
+ }
229
+
230
+ // Wait for animation to complete
231
+ await this.delay(this.params.explosion_duration || 400);
232
+ }
233
+
234
+ /**
235
+ * Hide current calibration point (removes element)
236
+ */
237
+ async hidePoint(): Promise<void> {
238
+ if (this.currentPoint) {
239
+ this.currentPoint.remove();
240
+ this.currentPoint = null;
241
+ }
242
+ await this.delay(200);
243
+ }
244
+
245
+ /**
246
+ * Reset point state after explosion (keeps element for continued travel)
247
+ */
248
+ async resetPointAfterExplosion(): Promise<void> {
249
+ if (!this.currentPoint) return;
250
+
251
+ // Reset to normal size and opacity for next travel
252
+ this.currentPoint.classList.remove('animation-explosion');
253
+ this.currentPoint.style.transform = 'translate(-50%, -50%) scale(1)';
254
+ this.currentPoint.style.opacity = '1';
255
+ this.currentPoint.style.backgroundColor = this.params.point_color || '#ff0000';
256
+
257
+ await this.delay(50);
258
+ }
259
+
260
+ /**
261
+ * Wait for user click (in click mode)
262
+ */
263
+ waitForClick(): Promise<void> {
264
+ return new Promise((resolve) => {
265
+ const handleClick = () => {
266
+ document.removeEventListener('click', handleClick);
267
+ resolve();
268
+ };
269
+ document.addEventListener('click', handleClick);
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Show calibration result
275
+ * @param success Whether calibration succeeded
276
+ * @param canRetry Whether a retry button should be shown on failure
277
+ * @returns 'retry' if user chose to retry, 'continue' otherwise
278
+ */
279
+ async showResult(success: boolean, canRetry: boolean = false): Promise<'retry' | 'continue'> {
280
+ const result = document.createElement('div');
281
+ result.className = 'tobii-calibration-result';
282
+ result.setAttribute('role', 'alert');
283
+ result.setAttribute('aria-live', 'assertive');
284
+
285
+ if (success) {
286
+ result.innerHTML = `
287
+ <div class="tobii-calibration-result-content success">
288
+ <h2>Calibration Successful!</h2>
289
+ <p>Continuing automatically...</p>
290
+ </div>
291
+ `;
292
+
293
+ this.container.appendChild(result);
294
+
295
+ // Auto-advance after showing result
296
+ await this.delay(this.params.success_display_duration || 2000);
297
+ result.remove();
298
+ return 'continue';
299
+ }
300
+
301
+ // Failure case: show buttons
302
+ const buttonsHTML = canRetry
303
+ ? `<button class="calibration-retry-btn">Retry</button>
304
+ <button class="calibration-continue-btn" style="margin-left: 10px;">Continue</button>`
305
+ : `<button class="calibration-continue-btn">Continue</button>`;
306
+
307
+ result.innerHTML = `
308
+ <div class="tobii-calibration-result-content error">
309
+ <h2>Calibration Failed</h2>
310
+ <p>Please try again or continue.</p>
311
+ ${buttonsHTML}
312
+ </div>
313
+ `;
314
+
315
+ this.container.appendChild(result);
316
+
317
+ return new Promise((resolve) => {
318
+ const retryBtn = result.querySelector('.calibration-retry-btn');
319
+ const continueBtn = result.querySelector('.calibration-continue-btn');
320
+
321
+ retryBtn?.addEventListener('click', () => {
322
+ result.remove();
323
+ resolve('retry');
324
+ });
325
+
326
+ continueBtn?.addEventListener('click', () => {
327
+ result.remove();
328
+ resolve('continue');
329
+ });
330
+ });
331
+ }
332
+
333
+ /**
334
+ * Reset display state for a retry attempt
335
+ */
336
+ resetForRetry(): void {
337
+ this.container.innerHTML = '';
338
+ this.currentPoint = null;
339
+ this.currentX = 0.5;
340
+ this.currentY = 0.5;
341
+ }
342
+
343
+ /**
344
+ * Clear display
345
+ */
346
+ clear(): void {
347
+ this.container.innerHTML = '';
348
+ if (this.progressElement) {
349
+ this.progressElement.textContent = '';
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Delay helper
355
+ */
356
+ private delay(ms: number): Promise<void> {
357
+ return new Promise((resolve) => setTimeout(resolve, ms));
358
+ }
359
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,297 @@
1
+ /**
2
+ * @title Tobii Calibration
3
+ * @description jsPsych plugin for Tobii eye tracker calibration. Provides a visual
4
+ * calibration procedure with animated points and real-time feedback.
5
+ * @version 1.0.0
6
+ * @author jsPsych Team
7
+ * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-calibration#readme Documentation}
8
+ */
9
+ import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';
10
+ declare const info: {
11
+ readonly name: "tobii-calibration";
12
+ readonly version: string;
13
+ readonly parameters: {
14
+ /** Number of calibration points (5 or 9) */
15
+ readonly calibration_points: {
16
+ readonly type: ParameterType.INT;
17
+ readonly default: 9;
18
+ };
19
+ /** Calibration mode: click or view */
20
+ readonly calibration_mode: {
21
+ readonly type: ParameterType.STRING;
22
+ readonly default: "view";
23
+ };
24
+ /** Size of calibration points in pixels */
25
+ readonly point_size: {
26
+ readonly type: ParameterType.INT;
27
+ readonly default: 20;
28
+ };
29
+ /** Color of calibration points */
30
+ readonly point_color: {
31
+ readonly type: ParameterType.STRING;
32
+ readonly default: "#ff0000";
33
+ };
34
+ /** Duration to show each point before data collection (ms) - allows user to fixate */
35
+ readonly point_duration: {
36
+ readonly type: ParameterType.INT;
37
+ readonly default: 500;
38
+ };
39
+ /** Show progress indicator */
40
+ readonly show_progress: {
41
+ readonly type: ParameterType.BOOL;
42
+ readonly default: true;
43
+ };
44
+ /** Custom calibration points */
45
+ readonly custom_points: {
46
+ readonly type: ParameterType.COMPLEX;
47
+ readonly default: null;
48
+ };
49
+ /** Instructions text */
50
+ readonly instructions: {
51
+ readonly type: ParameterType.STRING;
52
+ readonly default: "Look at each point as it appears on the screen. Keep your gaze fixed on each point until it disappears.";
53
+ };
54
+ /** Button text for click mode */
55
+ readonly button_text: {
56
+ readonly type: ParameterType.STRING;
57
+ readonly default: "Start Calibration";
58
+ };
59
+ /** Background color of the calibration container */
60
+ readonly background_color: {
61
+ readonly type: ParameterType.STRING;
62
+ readonly default: "#808080";
63
+ };
64
+ /** Primary button color */
65
+ readonly button_color: {
66
+ readonly type: ParameterType.STRING;
67
+ readonly default: "#007bff";
68
+ };
69
+ /** Primary button hover color */
70
+ readonly button_hover_color: {
71
+ readonly type: ParameterType.STRING;
72
+ readonly default: "#0056b3";
73
+ };
74
+ /** Retry button color */
75
+ readonly retry_button_color: {
76
+ readonly type: ParameterType.STRING;
77
+ readonly default: "#dc3545";
78
+ };
79
+ /** Retry button hover color */
80
+ readonly retry_button_hover_color: {
81
+ readonly type: ParameterType.STRING;
82
+ readonly default: "#c82333";
83
+ };
84
+ /** Success message color */
85
+ readonly success_color: {
86
+ readonly type: ParameterType.STRING;
87
+ readonly default: "#28a745";
88
+ };
89
+ /** Error message color */
90
+ readonly error_color: {
91
+ readonly type: ParameterType.STRING;
92
+ readonly default: "#dc3545";
93
+ };
94
+ /** Maximum number of retry attempts allowed on calibration failure */
95
+ readonly max_retries: {
96
+ readonly type: ParameterType.INT;
97
+ readonly default: 1;
98
+ };
99
+ /** Duration of zoom in/out animations in ms */
100
+ readonly zoom_duration: {
101
+ readonly type: ParameterType.INT;
102
+ readonly default: 300;
103
+ };
104
+ /** Duration of explosion animation in ms */
105
+ readonly explosion_duration: {
106
+ readonly type: ParameterType.INT;
107
+ readonly default: 400;
108
+ };
109
+ /** Duration to show success result before auto-advancing in ms */
110
+ readonly success_display_duration: {
111
+ readonly type: ParameterType.INT;
112
+ readonly default: 2000;
113
+ };
114
+ /** Duration to show instructions before auto-advancing in view mode in ms */
115
+ readonly instruction_display_duration: {
116
+ readonly type: ParameterType.INT;
117
+ readonly default: 3000;
118
+ };
119
+ };
120
+ readonly data: {
121
+ /** Calibration success status */
122
+ readonly calibration_success: {
123
+ readonly type: ParameterType.BOOL;
124
+ };
125
+ /** Number of calibration points used */
126
+ readonly num_points: {
127
+ readonly type: ParameterType.INT;
128
+ };
129
+ /** Calibration mode used */
130
+ readonly mode: {
131
+ readonly type: ParameterType.STRING;
132
+ };
133
+ /** Full calibration result data */
134
+ readonly calibration_data: {
135
+ readonly type: ParameterType.COMPLEX;
136
+ };
137
+ /** Number of calibration attempts made */
138
+ readonly num_attempts: {
139
+ readonly type: ParameterType.INT;
140
+ };
141
+ };
142
+ };
143
+ type Info = typeof info;
144
+ declare class TobiiCalibrationPlugin implements JsPsychPlugin<Info> {
145
+ private jsPsych;
146
+ static info: {
147
+ readonly name: "tobii-calibration";
148
+ readonly version: string;
149
+ readonly parameters: {
150
+ /** Number of calibration points (5 or 9) */
151
+ readonly calibration_points: {
152
+ readonly type: ParameterType.INT;
153
+ readonly default: 9;
154
+ };
155
+ /** Calibration mode: click or view */
156
+ readonly calibration_mode: {
157
+ readonly type: ParameterType.STRING;
158
+ readonly default: "view";
159
+ };
160
+ /** Size of calibration points in pixels */
161
+ readonly point_size: {
162
+ readonly type: ParameterType.INT;
163
+ readonly default: 20;
164
+ };
165
+ /** Color of calibration points */
166
+ readonly point_color: {
167
+ readonly type: ParameterType.STRING;
168
+ readonly default: "#ff0000";
169
+ };
170
+ /** Duration to show each point before data collection (ms) - allows user to fixate */
171
+ readonly point_duration: {
172
+ readonly type: ParameterType.INT;
173
+ readonly default: 500;
174
+ };
175
+ /** Show progress indicator */
176
+ readonly show_progress: {
177
+ readonly type: ParameterType.BOOL;
178
+ readonly default: true;
179
+ };
180
+ /** Custom calibration points */
181
+ readonly custom_points: {
182
+ readonly type: ParameterType.COMPLEX;
183
+ readonly default: null;
184
+ };
185
+ /** Instructions text */
186
+ readonly instructions: {
187
+ readonly type: ParameterType.STRING;
188
+ readonly default: "Look at each point as it appears on the screen. Keep your gaze fixed on each point until it disappears.";
189
+ };
190
+ /** Button text for click mode */
191
+ readonly button_text: {
192
+ readonly type: ParameterType.STRING;
193
+ readonly default: "Start Calibration";
194
+ };
195
+ /** Background color of the calibration container */
196
+ readonly background_color: {
197
+ readonly type: ParameterType.STRING;
198
+ readonly default: "#808080";
199
+ };
200
+ /** Primary button color */
201
+ readonly button_color: {
202
+ readonly type: ParameterType.STRING;
203
+ readonly default: "#007bff";
204
+ };
205
+ /** Primary button hover color */
206
+ readonly button_hover_color: {
207
+ readonly type: ParameterType.STRING;
208
+ readonly default: "#0056b3";
209
+ };
210
+ /** Retry button color */
211
+ readonly retry_button_color: {
212
+ readonly type: ParameterType.STRING;
213
+ readonly default: "#dc3545";
214
+ };
215
+ /** Retry button hover color */
216
+ readonly retry_button_hover_color: {
217
+ readonly type: ParameterType.STRING;
218
+ readonly default: "#c82333";
219
+ };
220
+ /** Success message color */
221
+ readonly success_color: {
222
+ readonly type: ParameterType.STRING;
223
+ readonly default: "#28a745";
224
+ };
225
+ /** Error message color */
226
+ readonly error_color: {
227
+ readonly type: ParameterType.STRING;
228
+ readonly default: "#dc3545";
229
+ };
230
+ /** Maximum number of retry attempts allowed on calibration failure */
231
+ readonly max_retries: {
232
+ readonly type: ParameterType.INT;
233
+ readonly default: 1;
234
+ };
235
+ /** Duration of zoom in/out animations in ms */
236
+ readonly zoom_duration: {
237
+ readonly type: ParameterType.INT;
238
+ readonly default: 300;
239
+ };
240
+ /** Duration of explosion animation in ms */
241
+ readonly explosion_duration: {
242
+ readonly type: ParameterType.INT;
243
+ readonly default: 400;
244
+ };
245
+ /** Duration to show success result before auto-advancing in ms */
246
+ readonly success_display_duration: {
247
+ readonly type: ParameterType.INT;
248
+ readonly default: 2000;
249
+ };
250
+ /** Duration to show instructions before auto-advancing in view mode in ms */
251
+ readonly instruction_display_duration: {
252
+ readonly type: ParameterType.INT;
253
+ readonly default: 3000;
254
+ };
255
+ };
256
+ readonly data: {
257
+ /** Calibration success status */
258
+ readonly calibration_success: {
259
+ readonly type: ParameterType.BOOL;
260
+ };
261
+ /** Number of calibration points used */
262
+ readonly num_points: {
263
+ readonly type: ParameterType.INT;
264
+ };
265
+ /** Calibration mode used */
266
+ readonly mode: {
267
+ readonly type: ParameterType.STRING;
268
+ };
269
+ /** Full calibration result data */
270
+ readonly calibration_data: {
271
+ readonly type: ParameterType.COMPLEX;
272
+ };
273
+ /** Number of calibration attempts made */
274
+ readonly num_attempts: {
275
+ readonly type: ParameterType.INT;
276
+ };
277
+ };
278
+ };
279
+ constructor(jsPsych: JsPsych);
280
+ private static removeStyles;
281
+ private injectStyles;
282
+ trial(display_element: HTMLElement, trial: TrialType<Info>): Promise<void>;
283
+ /**
284
+ * Validate custom calibration points
285
+ */
286
+ private validateCustomPoints;
287
+ /**
288
+ * Get standard calibration points for the given grid size
289
+ */
290
+ private getCalibrationPoints;
291
+ /**
292
+ * Delay helper
293
+ */
294
+ private delay;
295
+ }
296
+ export default TobiiCalibrationPlugin;
297
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAO3E,QAAA,MAAM,IAAI;;;;QAIN,4CAA4C;;;;;QAK5C,sCAAsC;;;;;QAKtC,2CAA2C;;;;;QAK3C,kCAAkC;;;;;QAKlC,sFAAsF;;;;;QAKtF,8BAA8B;;;;;QAK9B,gCAAgC;;;;;QAKhC,wBAAwB;;;;;QAMxB,iCAAiC;;;;;QAKjC,oDAAoD;;;;;QAKpD,2BAA2B;;;;;QAK3B,iCAAiC;;;;;QAKjC,yBAAyB;;;;;QAKzB,+BAA+B;;;;;QAK/B,4BAA4B;;;;;QAK5B,0BAA0B;;;;;QAK1B,sEAAsE;;;;;QAKtE,+CAA+C;;;;;QAK/C,4CAA4C;;;;;QAK5C,kEAAkE;;;;;QAKlE,6EAA6E;;;;;;;QAO7E,iCAAiC;;;;QAIjC,wCAAwC;;;;QAIxC,4BAA4B;;;;QAI5B,mCAAmC;;;;QAInC,0CAA0C;;;;;CAK7C,CAAC;AAEF,KAAK,IAAI,GAAG,OAAO,IAAI,CAAC;AAExB,cAAM,sBAAuB,YAAW,aAAa,CAAC,IAAI,CAAC;IAG7C,OAAO,CAAC,OAAO;IAF3B,MAAM,CAAC,IAAI;;;;YAtIT,4CAA4C;;;;;YAK5C,sCAAsC;;;;;YAKtC,2CAA2C;;;;;YAK3C,kCAAkC;;;;;YAKlC,sFAAsF;;;;;YAKtF,8BAA8B;;;;;YAK9B,gCAAgC;;;;;YAKhC,wBAAwB;;;;;YAMxB,iCAAiC;;;;;YAKjC,oDAAoD;;;;;YAKpD,2BAA2B;;;;;YAK3B,iCAAiC;;;;;YAKjC,yBAAyB;;;;;YAKzB,+BAA+B;;;;;YAK/B,4BAA4B;;;;;YAK5B,0BAA0B;;;;;YAK1B,sEAAsE;;;;;YAKtE,+CAA+C;;;;;YAK/C,4CAA4C;;;;;YAK5C,kEAAkE;;;;;YAKlE,6EAA6E;;;;;;;YAO7E,iCAAiC;;;;YAIjC,wCAAwC;;;;YAIxC,4BAA4B;;;;YAI5B,mCAAmC;;;;YAInC,0CAA0C;;;;;MAUzB;gBAEC,OAAO,EAAE,OAAO;IAEpC,OAAO,CAAC,MAAM,CAAC,YAAY;IAO3B,OAAO,CAAC,YAAY;IA4Md,KAAK,CAAC,eAAe,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAuHhF;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA0B5B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqH5B;;OAEG;IACH,OAAO,CAAC,KAAK;CAGd;AAED,eAAe,sBAAsB,CAAC"}