@jspsych/plugin-tobii-user-position 0.1.1 → 0.2.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/dist/index.browser.js +2 -2
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +2 -2
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.browser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var jsPsychTobiiUserPosition = (function (jspsych) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var version = "0.
|
|
4
|
+
var version = "0.2.1";
|
|
5
5
|
|
|
6
6
|
class PositionDisplay {
|
|
7
7
|
constructor(container, options) {
|
|
@@ -687,4 +687,4 @@ var jsPsychTobiiUserPosition = (function (jspsych) {
|
|
|
687
687
|
return TobiiUserPositionPlugin;
|
|
688
688
|
|
|
689
689
|
})(jsPsychModule);
|
|
690
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-tobii-user-position@0.
|
|
690
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-tobii-user-position@0.2.1/dist/index.browser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.1.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.2.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.2.1\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.2.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":["ParameterType"],"mappings":";;;EAEE,IAAA,OAAA,GAAW,OAAA;;ECEN,MAAM,eAAA,CAAgB;EAAA,EACzB,WAAA,CAAY,WAAW,OAAA,EAAS;EAE5B,IAAA,IAAA,CAAK,SAAA,GAAY,GAAA;EACjB,IAAA,IAAA,CAAK,UAAA,GAAa,GAAA;EAClB,IAAA,IAAA,CAAK,cAAA,GAAiB,GAAA;EACtB,IAAA,IAAA,CAAK,cAAA,GAAiB,GAAA;EACtB,IAAA,IAAA,CAAK,kBAAA,GAAqB,CAAA;EAC1B,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;EACjB,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;EACf,IAAA,IAAA,CAAK,aAAA,EAAc;EAAA,EACvB;EAAA,EACA,aAAA,GAAgB;EACZ,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY,EAAA;EAE3B,IAAA,IAAA,CAAK,cAAA,GAAiB,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EAClD,IAAA,IAAA,CAAK,eAAe,SAAA,GAAY,6BAAA;EAChC,IAAA,IAAA,CAAK,cAAA,CAAe,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,OAAA;EAC/C,IAAA,IAAA,CAAK,SAAA,CAAU,WAAA,CAAY,IAAA,CAAK,cAAc,CAAA;EAE9C,IAAA,MAAM,cAAA,GAAiB,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EACnD,IAAA,cAAA,CAAe,SAAA,GAAY,2BAAA;EAC3B,IAAA,IAAA,CAAK,SAAA,CAAU,YAAY,cAAc,CAAA;EAEzC,IAAA,IAAA,CAAK,kBAAA,GAAqB,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EACtD,IAAA,IAAA,CAAK,mBAAmB,SAAA,GAAY,oBAAA;EACpC,IAAA,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,MAAA,EAAQ,KAAK,CAAA;EAClD,IAAA,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,YAAA,EAAc,gCAAgC,CAAA;EACnF,IAAA,IAAA,CAAK,kBAAA,CAAmB,MAAM,OAAA,GAAU;AAAA;AAAA,aAAA,EAEjC,KAAK,SAAS,CAAA;AAAA,cAAA,EACb,KAAK,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;EAOvB,IAAA,cAAA,CAAe,WAAA,CAAY,KAAK,kBAAkB,CAAA;EAElD,IAAA,MAAM,WAAA,GAAc,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EAChD,IAAA,WAAA,CAAY,MAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;EAU5B,IAAA,IAAA,CAAK,kBAAA,CAAmB,YAAY,WAAW,CAAA;EAE/C,IAAA,IAAA,CAAK,kBAAA,GAAqB,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EACtD,IAAA,IAAA,CAAK,mBAAmB,SAAA,GAAY,oBAAA;EACpC,IAAA,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,aAAA,EAAe,MAAM,CAAA;EAC1D,IAAA,IAAA,CAAK,kBAAA,CAAmB,MAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;EAOxC,IAAA,IAAA,CAAK,kBAAA,CAAmB,WAAA,CAAY,IAAA,CAAK,kBAAkB,CAAA;EAE3D,IAAA,IAAA,CAAK,aAAA,EAAc;EAEnB,IAAA,IAAI,IAAA,CAAK,QAAQ,oBAAA,EAAsB;EACnC,MAAA,IAAA,CAAK,oBAAA,GAAuB,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EACxD,MAAA,IAAA,CAAK,oBAAA,CAAqB,MAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAA,CAAA;EAW1C,MAAA,cAAA,CAAe,WAAA,CAAY,KAAK,oBAAoB,CAAA;EAEpD,MAAA,MAAM,UAAA,GAAa,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EAC/C,MAAA,UAAA,CAAW,WAAA,GAAc,OAAA;EACzB,MAAA,UAAA,CAAW,MAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAA,CAAA;EAO3B,MAAA,cAAA,CAAe,YAAY,UAAU,CAAA;EACrC,MAAA,MAAM,QAAA,GAAW,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EAC7C,MAAA,QAAA,CAAS,WAAA,GAAc,KAAA;EACvB,MAAA,QAAA,CAAS,MAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAA,CAAA;EAOzB,MAAA,cAAA,CAAe,YAAY,QAAQ,CAAA;EACnC,MAAA,IAAA,CAAK,eAAA,GAAkB,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EACnD,MAAA,IAAA,CAAK,eAAA,CAAgB,MAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAA,EAM3B,IAAA,CAAK,QAAQ,SAAS,CAAA;AAAA;AAAA;AAAA,MAAA,CAAA;EAIhC,MAAA,IAAA,CAAK,oBAAA,CAAqB,WAAA,CAAY,IAAA,CAAK,eAAe,CAAA;EAE1D,MAAA,MAAM,aAAA,GAAgB,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EAClD,MAAA,aAAA,CAAc,MAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAA,CAAA;EAU9B,MAAA,IAAA,CAAK,oBAAA,CAAqB,YAAY,aAAa,CAAA;EAAA,IACvD;EAEA,IAAA,IAAA,CAAK,eAAA,GAAkB,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;EACnD,IAAA,IAAA,CAAK,gBAAgB,SAAA,GAAY,yBAAA;EACjC,IAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,MAAA,EAAQ,QAAQ,CAAA;EAClD,IAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,WAAA,EAAa,QAAQ,CAAA;EACvD,IAAA,IAAA,CAAK,eAAA,CAAgB,MAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;EAMrC,IAAA,IAAA,CAAK,SAAA,CAAU,WAAA,CAAY,IAAA,CAAK,eAAe,CAAA;EAAA,EACnD;EAAA,EACA,aAAA,GAAgB;EAEZ,IAAA,MAAM,SAAA,GAAY,GAAA;EAClB,IAAA,MAAM,UAAA,GAAa,GAAA;EACnB,IAAA,MAAM,GAAA,GAAM,QAAA,CAAS,eAAA,CAAgB,4BAAA,EAA8B,KAAK,CAAA;EACxE,IAAA,GAAA,CAAI,YAAA,CAAa,OAAA,EAAS,CAAA,EAAG,SAAS,CAAA,CAAE,CAAA;EACxC,IAAA,GAAA,CAAI,YAAA,CAAa,QAAA,EAAU,CAAA,EAAG,UAAU,CAAA,CAAE,CAAA;EAC1C,IAAA,GAAA,CAAI,aAAa,SAAA,EAAW,CAAA,IAAA,EAAO,SAAS,CAAA,CAAA,EAAI,UAAU,CAAA,CAAE,CAAA;EAC5D,IAAA,GAAA,CAAI,MAAM,OAAA,GAAU,oBAAA;EAEpB,IAAA,MAAM,WAAA,GAAc,QAAA,CAAS,eAAA,CAAgB,4BAAA,EAA8B,SAAS,CAAA;EACpF,IAAA,WAAA,CAAY,YAAA,CAAa,MAAM,IAAI,CAAA;EACnC,IAAA,WAAA,CAAY,YAAA,CAAa,MAAM,IAAI,CAAA;EACnC,IAAA,WAAA,CAAY,YAAA,CAAa,MAAM,IAAI,CAAA;EACnC,IAAA,WAAA,CAAY,YAAA,CAAa,MAAM,IAAI,CAAA;EACnC,IAAA,WAAA,CAAY,YAAA,CAAa,QAAQ,MAAM,CAAA;EACvC,IAAA,WAAA,CAAY,YAAA,CAAa,UAAU,MAAM,CAAA;EACzC,IAAA,WAAA,CAAY,YAAA,CAAa,gBAAgB,GAAG,CAAA;EAC5C,IAAA,WAAA,CAAY,YAAA,CAAa,oBAAoB,KAAK,CAAA;EAClD,IAAA,GAAA,CAAI,YAAY,WAAW,CAAA;EAE3B,IAAA,IAAA,CAAK,cAAA,GAAiB,QAAA,CAAS,eAAA,CAAgB,4BAAA,EAA8B,QAAQ,CAAA;EACrF,IAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,IAAA,EAAM,IAAI,CAAA;EAC3C,IAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,IAAA,EAAM,IAAI,CAAA;EAC3C,IAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,GAAA,EAAK,IAAI,CAAA;EAC1C,IAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,MAAA,EAAQ,IAAA,CAAK,QAAQ,SAAS,CAAA;EAC/D,IAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,QAAA,EAAU,MAAM,CAAA;EACjD,IAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,cAAA,EAAgB,GAAG,CAAA;EACpD,IAAA,GAAA,CAAI,WAAA,CAAY,KAAK,cAAc,CAAA;EAEnC,IAAA,IAAA,CAAK,eAAA,GAAkB,QAAA,CAAS,eAAA,CAAgB,4BAAA,EAA8B,QAAQ,CAAA;EACtF,IAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,IAAA,EAAM,IAAI,CAAA;EAC5C,IAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,IAAA,EAAM,IAAI,CAAA;EAC5C,IAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,GAAA,EAAK,IAAI,CAAA;EAC3C,IAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,MAAA,EAAQ,IAAA,CAAK,QAAQ,SAAS,CAAA;EAChE,IAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,QAAA,EAAU,MAAM,CAAA;EAClD,IAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,cAAA,EAAgB,GAAG,CAAA;EACrD,IAAA,GAAA,CAAI,WAAA,CAAY,KAAK,eAAe,CAAA;EAEpC,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,eAAA,CAAgB,4BAAA,EAA8B,MAAM,CAAA;EAC1E,IAAA,IAAA,CAAK,YAAA,CAAa,KAAK,wBAAwB,CAAA;EAC/C,IAAA,IAAA,CAAK,YAAA,CAAa,QAAQ,MAAM,CAAA;EAChC,IAAA,IAAA,CAAK,YAAA,CAAa,UAAU,MAAM,CAAA;EAClC,IAAA,IAAA,CAAK,YAAA,CAAa,gBAAgB,GAAG,CAAA;EACrC,IAAA,GAAA,CAAI,YAAY,IAAI,CAAA;EACpB,IAAA,IAAA,CAAK,kBAAA,CAAmB,YAAY,GAAG,CAAA;EAAA,EAC3C;EAAA;EAAA;EAAA;EAAA,EAIA,eAAe,YAAA,EAAc;EACzB,IAAA,IAAI,CAAC,YAAA,EAAc;EACf,MAAA,IAAA,CAAK,UAAA,EAAW;EAChB,MAAA;EAAA,IACJ;EAEA,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,KAAA,EAAO,aAAa,MAAA,EAAQ,YAAA,CAAa,SAAA,EAAW,YAAA,CAAa,UAAU,CAAA;EAC7H,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,KAAA,EAAO,aAAa,MAAA,EAAQ,YAAA,CAAa,SAAA,EAAW,YAAA,CAAa,UAAU,CAAA;EAC7H,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,KAAA,EAAO,aAAa,MAAA,EAAQ,YAAA,CAAa,SAAA,EAAW,YAAA,CAAa,UAAU,CAAA;EAE7H,IAAA,IAAA,CAAK,iBAAA,CAAkB,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,YAAY,CAAA;EAErD,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,oBAAA,IAAwB,IAAA,KAAS,IAAA,EAAM;EACpD,MAAA,IAAA,CAAK,kBAAkB,IAAI,CAAA;EAAA,IAC/B;EAEA,IAAA,IAAA,CAAK,oBAAoB,YAAY,CAAA;EAErC,IAAA,IAAA,CAAK,qBAAA,CAAsB,IAAA,EAAM,IAAA,EAAM,IAAI,CAAA;EAAA,EAC/C;EAAA,EACA,kBAAA,CAAmB,IAAA,EAAM,KAAA,EAAO,SAAA,EAAW,UAAA,EAAY;EACnD,IAAA,IAAI,SAAA,IAAa,UAAA,IAAc,IAAA,KAAS,IAAA,IAAQ,UAAU,IAAA,EAAM;EAC5D,MAAA,OAAA,CAAQ,OAAO,KAAA,IAAS,CAAA;EAAA,IAC5B,CAAA,MAAA,IACS,SAAA,IAAa,IAAA,KAAS,IAAA,EAAM;EACjC,MAAA,OAAO,IAAA;EAAA,IACX,CAAA,MAAA,IACS,UAAA,IAAc,KAAA,KAAU,IAAA,EAAM;EACnC,MAAA,OAAO,KAAA;EAAA,IACX;EACA,IAAA,OAAO,IAAA;EAAA,EACX;EAAA,EACA,iBAAA,CAAkB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,aAAA,EAAe;EACtC,IAAA,IAAI,CAAA,KAAM,IAAA,IAAQ,CAAA,KAAM,IAAA,EAAM;EAC1B,MAAA,IAAA,CAAK,kBAAA,CAAmB,MAAM,OAAA,GAAU,KAAA;EACxC,MAAA;EAAA,IACJ;EACA,IAAA,IAAA,CAAK,kBAAA,CAAmB,MAAM,OAAA,GAAU,GAAA;EAIxC,IAAA,MAAM,OAAA,GAAA,CAAW,CAAA,GAAI,GAAA,IAAO,IAAA,CAAK,SAAA,GAAY,GAAA;EAC7C,IAAA,MAAM,OAAA,GAAA,CAAW,GAAA,GAAM,CAAA,IAAK,IAAA,CAAK,UAAA,GAAa,GAAA;EAI9C,IAAA,IAAI,QAAQ,IAAA,CAAK,kBAAA;EACjB,IAAA,IAAI,MAAM,IAAA,EAAM;EAEZ,MAAA,KAAA,GAAQ,IAAA,CAAK,cAAA,GAAiB,CAAA,IAAK,IAAA,CAAK,iBAAiB,IAAA,CAAK,cAAA,CAAA;EAC9D,MAAA,KAAA,GAAQ,IAAA,CAAK,IAAI,IAAA,CAAK,cAAA,EAAgB,KAAK,GAAA,CAAI,IAAA,CAAK,cAAA,EAAgB,KAAK,CAAC,CAAA;EAAA,IAC9E;EACA,IAAA,IAAA,CAAK,kBAAA,CAAmB,MAAM,SAAA,GAAY,CAAA,sBAAA,EAAyB,OAAO,CAAA,iBAAA,EAAoB,OAAO,cAAc,KAAK,CAAA,CAAA,CAAA;EAAA,EAC5H;EAAA,EACA,kBAAkB,CAAA,EAAG;EACjB,IAAA,IAAI,CAAC,IAAA,CAAK,eAAA;EACN,MAAA;EAGJ,IAAA,MAAM,cAAc,CAAA,GAAI,GAAA;EACxB,IAAA,IAAA,CAAK,eAAA,CAAgB,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG,WAAW,CAAA,CAAA,CAAA;EAElD,IAAA,MAAM,OAAA,GAAU,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,qBAAA;EACnC,IAAA,MAAM,OAAA,GAAU,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,qBAAA;EACnC,IAAA,MAAM,OAAA,GAAU,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,qBAAA;EACnC,IAAA,MAAM,OAAA,GAAU,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,qBAAA;EACnC,IAAA,IAAI,KAAA;EACJ,IAAA,IAAI,CAAA,IAAK,OAAA,IAAW,CAAA,IAAK,OAAA,EAAS;EAC9B,MAAA,KAAA,GAAQ,KAAK,OAAA,CAAQ,SAAA;EAAA,IACzB,CAAA,MAAA,IACS,CAAA,IAAK,OAAA,IAAW,CAAA,IAAK,OAAA,EAAS;EACnC,MAAA,KAAA,GAAQ,KAAK,OAAA,CAAQ,SAAA;EAAA,IACzB,CAAA,MACK;EACD,MAAA,KAAA,GAAQ,KAAK,OAAA,CAAQ,SAAA;EAAA,IACzB;EACA,IAAA,IAAA,CAAK,eAAA,CAAgB,MAAM,UAAA,GAAa,KAAA;EAAA,EAC5C;EAAA,EACA,oBAAoB,YAAA,EAAc;EAE9B,IAAA,IAAI,KAAK,cAAA,EAAgB;EACrB,MAAA,MAAM,YAAY,YAAA,CAAa,SAAA,GAAY,KAAK,OAAA,CAAQ,SAAA,GAAY,KAAK,OAAA,CAAQ,SAAA;EACjF,MAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,MAAA,EAAQ,SAAS,CAAA;EAAA,IACtD;EAEA,IAAA,IAAI,KAAK,eAAA,EAAiB;EACtB,MAAA,MAAM,aAAa,YAAA,CAAa,UAAA,GAAa,KAAK,OAAA,CAAQ,SAAA,GAAY,KAAK,OAAA,CAAQ,SAAA;EACnF,MAAA,IAAA,CAAK,eAAA,CAAgB,YAAA,CAAa,MAAA,EAAQ,UAAU,CAAA;EAAA,IACxD;EAAA,EACJ;EAAA,EACA,qBAAA,CAAsB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG;EAC3B,IAAA,IAAI,CAAC,IAAA,CAAK,eAAA;EACN,MAAA;EACJ,IAAA,IAAI,CAAA,KAAM,IAAA,IAAQ,CAAA,KAAM,IAAA,IAAQ,MAAM,IAAA,EAAM;EACxC,MAAA,IAAA,CAAK,gBAAgB,WAAA,GACjB,sEAAA;EACJ,MAAA,IAAA,CAAK,eAAA,CAAgB,KAAA,CAAM,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,SAAA;EAChD,MAAA;EAAA,IACJ;EACA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,qBAAA,CAAsB,CAAA,EAAG,GAAG,CAAC,CAAA;EAClD,IAAA,IAAI,QAAA;EACJ,IAAA,IAAI,KAAA;EACJ,IAAA,IAAI,QAAQ,cAAA,EAAgB;EACxB,MAAA,QAAA,GAAW,yBAAA;EACX,MAAA,KAAA,GAAQ,KAAK,OAAA,CAAQ,SAAA;EAAA,IACzB,CAAA,MACK;EACD,MAAA,MAAM,SAAS,EAAC;EAEhB,MAAA,MAAM,gBAAA,GAAmB,KAAK,OAAA,CAAQ,qBAAA;EACtC,MAAA,IAAI,IAAI,GAAA,GAAM,gBAAA;EACV,QAAA,MAAA,CAAO,KAAK,YAAY,CAAA;EAAA,WAAA,IACnB,IAAI,GAAA,GAAM,gBAAA;EACf,QAAA,MAAA,CAAO,KAAK,WAAW,CAAA;EAE3B,MAAA,IAAI,IAAI,GAAA,GAAM,gBAAA;EACV,QAAA,MAAA,CAAO,KAAK,SAAS,CAAA;EAAA,WAAA,IAChB,IAAI,GAAA,GAAM,gBAAA;EACf,QAAA,MAAA,CAAO,KAAK,WAAW,CAAA;EAE3B,MAAA,MAAM,iBAAA,GAAoB,KAAK,OAAA,CAAQ,qBAAA;EACvC,MAAA,IAAI,IAAI,GAAA,GAAM,iBAAA;EACV,QAAA,MAAA,CAAO,KAAK,WAAW,CAAA;EAAA,WAAA,IAClB,IAAI,GAAA,GAAM,iBAAA;EACf,QAAA,MAAA,CAAO,KAAK,aAAa,CAAA;EAC7B,MAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;EACnB,QAAA,QAAA,GAAW,CAAA,OAAA,EAAU,MAAA,CAAO,IAAA,CAAK,OAAO,CAAC,CAAA,CAAA;EAAA,MAC7C,CAAA,MACK;EACD,QAAA,QAAA,GAAW,2BAAA;EAAA,MACf;EACA,MAAA,KAAA,GACI,OAAA,CAAQ,cAAA,KAAmB,MAAA,IACvB,OAAA,CAAQ,gBAAA,KAAqB,MAAA,IAC7B,OAAA,CAAQ,cAAA,KAAmB,MAAA,GACzB,IAAA,CAAK,OAAA,CAAQ,SAAA,GACb,KAAK,OAAA,CAAQ,SAAA;EAAA,IAC3B;EACA,IAAA,IAAA,CAAK,gBAAgB,WAAA,GAAc,QAAA;EACnC,IAAA,IAAA,CAAK,eAAA,CAAgB,MAAM,KAAA,GAAQ,KAAA;EAAA,EACvC;EAAA,EACA,qBAAA,CAAsB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG;EAE3B,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAA;EAChC,IAAA,IAAI,gBAAA;EACJ,IAAA,IAAI,OAAA,GAAU,KAAK,OAAA,CAAQ,qBAAA;EACvB,MAAA,gBAAA,GAAmB,MAAA;EAAA,SAAA,IACd,OAAA,GAAU,KAAK,OAAA,CAAQ,qBAAA;EAC5B,MAAA,gBAAA,GAAmB,MAAA;EAAA;EAEnB,MAAA,gBAAA,GAAmB,MAAA;EAEvB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAA;EAChC,IAAA,IAAI,cAAA;EACJ,IAAA,IAAI,OAAA,GAAU,KAAK,OAAA,CAAQ,qBAAA;EACvB,MAAA,cAAA,GAAiB,MAAA;EAAA,SAAA,IACZ,OAAA,GAAU,KAAK,OAAA,CAAQ,qBAAA;EAC5B,MAAA,cAAA,GAAiB,MAAA;EAAA;EAEjB,MAAA,cAAA,GAAiB,MAAA;EAErB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAA;EAChC,IAAA,IAAI,cAAA;EACJ,IAAA,IAAI,OAAA,GAAU,KAAK,OAAA,CAAQ,qBAAA;EACvB,MAAA,cAAA,GAAiB,MAAA;EAAA,SAAA,IACZ,OAAA,GAAU,KAAK,OAAA,CAAQ,qBAAA;EAC5B,MAAA,cAAA,GAAiB,MAAA;EAAA;EAEjB,MAAA,cAAA,GAAiB,MAAA;EACrB,IAAA,MAAM,cAAA,GAAiB,gBAAA,KAAqB,MAAA,IAAU,cAAA,KAAmB,UAAU,cAAA,KAAmB,MAAA;EACtG,IAAA,OAAO;EAAA,MACH,cAAA;EAAA,MACA,gBAAA;EAAA,MACA,cAAA;EAAA,MACA,cAAA;EAAA,MACA,QAAA,EAAU,CAAA;EAAA,MACV,QAAA,EAAU,CAAA;EAAA,MACV,QAAA,EAAU;EAAA,KACd;EAAA,EACJ;EAAA,EACA,UAAA,GAAa;EACT,IAAA,IAAI,KAAK,eAAA,EAAiB;EACtB,MAAA,IAAA,CAAK,gBAAgB,WAAA,GAAc,6BAAA;EACnC,MAAA,IAAA,CAAK,eAAA,CAAgB,KAAA,CAAM,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,SAAA;EAAA,IACpD;EACA,IAAA,IAAA,CAAK,kBAAA,CAAmB,MAAM,OAAA,GAAU,KAAA;EAAA,EAC5C;EAAA;EAAA;EAAA;EAAA,EAIA,kBAAkB,YAAA,EAAc;EAC5B,IAAA,IAAI,CAAC,YAAA,EAAc;EACf,MAAA,OAAO;EAAA,QACH,cAAA,EAAgB,KAAA;EAAA,QAChB,gBAAA,EAAkB,MAAA;EAAA,QAClB,cAAA,EAAgB,MAAA;EAAA,QAChB,cAAA,EAAgB,MAAA;EAAA,QAChB,QAAA,EAAU,IAAA;EAAA,QACV,QAAA,EAAU,IAAA;EAAA,QACV,QAAA,EAAU;EAAA,OACd;EAAA,IACJ;EACA,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,KAAA,EAAO,aAAa,MAAA,EAAQ,YAAA,CAAa,SAAA,EAAW,YAAA,CAAa,UAAU,CAAA;EAC7H,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,KAAA,EAAO,aAAa,MAAA,EAAQ,YAAA,CAAa,SAAA,EAAW,YAAA,CAAa,UAAU,CAAA;EAC7H,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,kBAAA,CAAmB,YAAA,CAAa,KAAA,EAAO,aAAa,MAAA,EAAQ,YAAA,CAAa,SAAA,EAAW,YAAA,CAAa,UAAU,CAAA;EAC7H,IAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,IAAA,IAAQ,SAAS,IAAA,EAAM;EACjD,MAAA,OAAO;EAAA,QACH,cAAA,EAAgB,KAAA;EAAA,QAChB,gBAAA,EAAkB,MAAA;EAAA,QAClB,cAAA,EAAgB,MAAA;EAAA,QAChB,cAAA,EAAgB,MAAA;EAAA,QAChB,QAAA,EAAU,IAAA;EAAA,QACV,QAAA,EAAU,IAAA;EAAA,QACV,QAAA,EAAU;EAAA,OACd;EAAA,IACJ;EACA,IAAA,OAAO,IAAA,CAAK,qBAAA,CAAsB,IAAA,EAAM,IAAA,EAAM,IAAI,CAAA;EAAA,EACtD;EAAA;EAAA;EAAA;EAAA,EAIA,OAAA,GAAU;EACN,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY,EAAA;EAAA,EAC/B;EACJ;;ECpZA,MAAM,IAAA,GAAc;EAAA,EAClB,IAAA,EAAM,qBAAA;EAAA,EACN,OAAA;EAAA,EACA,UAAA,EAAY;EAAA;EAAA,IAEV,QAAA,EAAU;EAAA,MACR,MAAMA,qBAAA,CAAc,GAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,OAAA,EAAS;EAAA,MACP,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,eAAA,EAAiB;EAAA,MACf,MAAMA,qBAAA,CAAc,GAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,sBAAA,EAAwB;EAAA,MACtB,MAAMA,qBAAA,CAAc,IAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,sBAAA,EAAwB;EAAA,MACtB,MAAMA,qBAAA,CAAc,IAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,WAAA,EAAa;EAAA,MACX,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,qBAAA,EAAuB;EAAA,MACrB,MAAMA,qBAAA,CAAc,IAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,gBAAA,EAAkB;EAAA,MAChB,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,UAAA,EAAY;EAAA,MACV,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,UAAA,EAAY;EAAA,MACV,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,UAAA,EAAY;EAAA,MACV,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,YAAA,EAAc;EAAA,MACZ,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,kBAAA,EAAoB;EAAA,MAClB,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,SAAA,EAAW;EAAA,MACT,MAAMA,qBAAA,CAAc,MAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,uBAAA,EAAyB;EAAA,MACvB,MAAMA,qBAAA,CAAc,KAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,uBAAA,EAAyB;EAAA,MACvB,MAAMA,qBAAA,CAAc,KAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,uBAAA,EAAyB;EAAA,MACvB,MAAMA,qBAAA,CAAc,KAAA;EAAA,MACpB,OAAA,EAAS;EAAA,KACX;EAAA;EAAA,IAEA,uBAAA,EAAyB;EAAA,MACvB,MAAMA,qBAAA,CAAc,KAAA;EAAA,MACpB,OAAA,EAAS;EAAA;EACX,GACF;EAAA,EACA,IAAA,EAAM;EAAA;EAAA,IAEJ,SAAA,EAAW;EAAA,MACT,MAAMA,qBAAA,CAAc;EAAA,KACtB;EAAA;EAAA,IAEA,SAAA,EAAW;EAAA,MACT,MAAMA,qBAAA,CAAc;EAAA,KACtB;EAAA;EAAA,IAEA,SAAA,EAAW;EAAA,MACT,MAAMA,qBAAA,CAAc;EAAA,KACtB;EAAA;EAAA,IAEA,aAAA,EAAe;EAAA,MACb,MAAMA,qBAAA,CAAc;EAAA,KACtB;EAAA;EAAA,IAEA,iBAAA,EAAmB;EAAA,MACjB,MAAMA,qBAAA,CAAc;EAAA,KACtB;EAAA;EAAA,IAEA,eAAA,EAAiB;EAAA,MACf,MAAMA,qBAAA,CAAc;EAAA,KACtB;EAAA;EAAA,IAEA,eAAA,EAAiB;EAAA,MACf,MAAMA,qBAAA,CAAc;EAAA,KACtB;EAAA;EAAA,IAEA,EAAA,EAAI;EAAA,MACF,MAAMA,qBAAA,CAAc;EAAA;EACtB;EAEJ,CAAA;EAIA,MAAM,uBAAA,CAAuD;EAAA,EAG3D,YAAoB,OAAA,EAAkB;EAAlB,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;EAAA,EAAmB;EAAA,EAFvC;EAAA,IAAA,IAAA,CAAO,IAAA,GAAO,IAAA;EAAA;EAAA,EAId,OAAe,YAAA,GAAqB;EAClC,IAAA,MAAM,EAAA,GAAK,QAAA,CAAS,cAAA,CAAe,4BAA4B,CAAA;EAC/D,IAAA,IAAI,EAAA,EAAI;EACN,MAAA,EAAA,CAAG,MAAA,EAAO;EAAA,IACZ;EAAA,EACF;EAAA,EAEQ,aAAa,KAAA,EAA8B;EAEjD,IAAA,uBAAA,CAAwB,YAAA,EAAa;EAErC,IAAA,MAAM,GAAA,GAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAA,EASK,MAAM,SAAS,CAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAA,EA4BR,MAAM,YAAY,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,0BAAA,EAQlB,MAAM,kBAAkB,CAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;EAwBhD,IAAA,MAAM,YAAA,GAAe,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;EACnD,IAAA,YAAA,CAAa,EAAA,GAAK,4BAAA;EAClB,IAAA,YAAA,CAAa,WAAA,GAAc,GAAA;EAC3B,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,YAAY,CAAA;EAAA,EACxC;EAAA,EAEA,KAAA,CAAM,iBAA8B,KAAA,EAAwB;EAC1D,IAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;EAEpC,MAAA,IAAA,CAAK,aAAa,KAAK,CAAA;EAGvB,MAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,OAAA,CAAQ,UAAA,CAAW,KAAA;EAC/C,MAAA,IAAI,CAAC,cAAA,EAAgB;EACnB,QAAA,MAAM,IAAI,MAAM,4BAA4B,CAAA;EAAA,MAC9C;EAGA,MAAA,eAAA,CAAgB,SAAA,GAAY;AAAA;AAAA;AAAA,MAAA,CAAA;EAK5B,MAAA,MAAM,YAAY,eAAA,CAAgB,aAAA;EAAA,QAChC;EAAA,OACF;EAGA,MAAA,MAAM,eAAA,GAAkB,IAAI,eAAA,CAAgB,SAAA,EAAW;EAAA,QACrD,SAAS,KAAA,CAAM,OAAA;EAAA,QACf,sBAAsB,KAAA,CAAM,sBAAA;EAAA,QAC5B,sBAAsB,KAAA,CAAM,sBAAA;EAAA,QAC5B,iBAAiB,KAAA,CAAM,gBAAA;EAAA,QACvB,WAAW,KAAA,CAAM,UAAA;EAAA,QACjB,WAAW,KAAA,CAAM,UAAA;EAAA,QACjB,WAAW,KAAA,CAAM,UAAA;EAAA,QACjB,UAAU,KAAA,CAAM,SAAA;EAAA,QAChB,uBAAuB,KAAA,CAAM,uBAAA;EAAA,QAC7B,uBAAuB,KAAA,CAAM,uBAAA;EAAA,QAC7B,uBAAuB,KAAA,CAAM,uBAAA;EAAA,QAC7B,uBAAuB,KAAA,CAAM;EAAA,OAC9B,CAAA;EAGD,MAAA,IAAI,cAAA,GAA2C,IAAA;EAC/C,MAAA,IAAI,KAAA,CAAM,aAAa,IAAA,EAAM;EAC3B,QAAA,cAAA,GAAiB,QAAA,CAAS,cAAc,QAAQ,CAAA;EAChD,QAAA,cAAA,CAAe,SAAA,GAAY,4BAAA;EAC3B,QAAA,cAAA,CAAe,cAAc,KAAA,CAAM,WAAA;EACnC,QAAA,IAAI,MAAM,qBAAA,EAAuB;EAC/B,UAAA,cAAA,CAAe,QAAA,GAAW,IAAA;EAAA,QAC5B;EACA,QAAA,SAAA,CAAU,YAAY,cAAc,CAAA;EAAA,MACtC;EAGA,MAAA,MAAM,kBAAqC,EAAC;EAC5C,MAAA,MAAM,SAAA,GAAY,YAAY,GAAA,EAAI;EAGlC,MAAA,MAAM,cAAA,GAAiB,YAAY,YAAY;EAC7C,QAAA,IAAI;EACF,UAAA,MAAM,YAAA,GAAe,MAAM,cAAA,CAAe,eAAA,EAAgB;EAC1D,UAAA,eAAA,CAAgB,eAAe,YAAY,CAAA;EAG3C,UAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,iBAAA,CAAkB,YAAY,CAAA;EAC9D,UAAA,eAAA,CAAgB,KAAK,OAAO,CAAA;EAG5B,UAAA,IAAI,cAAA,IAAkB,MAAM,qBAAA,EAAuB;EACjD,YAAA,cAAA,CAAe,QAAA,GAAW,CAAC,OAAA,CAAQ,cAAA;EAAA,UACrC;EAAA,QACF,SAAS,KAAA,EAAO;EACd,UAAA,OAAA,CAAQ,KAAA,CAAM,iCAAiC,KAAK,CAAA;EAAA,QACtD;EAAA,MACF,CAAA,EAAG,MAAM,eAAgB,CAAA;EAGzB,MAAA,MAAM,UAAU,MAAM;EACpB,QAAA,aAAA,CAAc,cAAc,CAAA;EAC5B,QAAA,eAAA,CAAgB,OAAA,EAAQ;EACxB,QAAA,eAAA,CAAgB,SAAA,GAAY,EAAA;EAC5B,QAAA,uBAAA,CAAwB,YAAA,EAAa;EAAA,MACvC,CAAA;EAGA,MAAA,MAAM,WAAW,MAAM;EAErB,QAAA,MAAM,eAAe,eAAA,CAAgB,MAAA;EAAA,UACnC,CAAC,MAAM,CAAA,CAAE,QAAA,KAAa,QAAQ,CAAA,CAAE,QAAA,KAAa,IAAA,IAAQ,CAAA,CAAE,QAAA,KAAa;EAAA,SACtE;EAEA,QAAA,IAAI,QAAA,GAAW,IAAA;EACf,QAAA,IAAI,QAAA,GAAW,IAAA;EACf,QAAA,IAAI,QAAA,GAAW,IAAA;EACf,QAAA,IAAI,YAAA,GAAuC,IAAA;EAE3C,QAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;EAC3B,UAAA,QAAA,GAAW,YAAA,CAAa,MAAA,CAAO,CAAC,GAAA,EAAK,CAAA,KAAM,MAAM,CAAA,CAAE,QAAA,EAAW,CAAC,CAAA,GAAI,YAAA,CAAa,MAAA;EAChF,UAAA,QAAA,GAAW,YAAA,CAAa,MAAA,CAAO,CAAC,GAAA,EAAK,CAAA,KAAM,MAAM,CAAA,CAAE,QAAA,EAAW,CAAC,CAAA,GAAI,YAAA,CAAa,MAAA;EAChF,UAAA,QAAA,GAAW,YAAA,CAAa,MAAA,CAAO,CAAC,GAAA,EAAK,CAAA,KAAM,MAAM,CAAA,CAAE,QAAA,EAAW,CAAC,CAAA,GAAI,YAAA,CAAa,MAAA;EAChF,UAAA,YAAA,GAAe,eAAA,CAAgB,eAAA,CAAgB,MAAA,GAAS,CAAC,CAAA;EAAA,QAC3D;EAEA,QAAA,MAAM,SAAA,GAAY;EAAA,UAChB,SAAA,EAAW,QAAA;EAAA,UACX,SAAA,EAAW,QAAA;EAAA,UACX,SAAA,EAAW,QAAA;EAAA,UACX,aAAA,EAAe,cAAc,cAAA,IAAkB,KAAA;EAAA,UAC/C,iBAAA,EAAmB,cAAc,gBAAA,IAAoB,MAAA;EAAA,UACrD,eAAA,EAAiB,cAAc,cAAA,IAAkB,MAAA;EAAA,UACjD,eAAA,EAAiB,cAAc,cAAA,IAAkB,MAAA;EAAA,UACjD,IAAI,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,GAAA,KAAQ,SAAS;EAAA,SAC9C;EAEA,QAAA,OAAA,EAAQ;EACR,QAAA,IAAA,CAAK,OAAA,CAAQ,YAAY,SAAS,CAAA;EAClC,QAAA,OAAA,EAAQ;EAAA,MACV,CAAA;EAGA,MAAA,IAAI,cAAA,EAAgB;EAClB,QAAA,cAAA,CAAe,gBAAA,CAAiB,SAAS,QAAQ,CAAA;EAAA,MACnD;EAGA,MAAA,IAAI,KAAA,CAAM,YAAY,IAAA,EAAM;EAC1B,QAAA,IAAA,CAAK,OAAA,CAAQ,SAAA,CAAU,UAAA,CAAW,QAAA,EAAU,MAAM,QAAQ,CAAA;EAAA,MAC5D;EAAA,IACF,CAAC,CAAA;EAAA,EACH;EACF;;;;;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var jsPsychTobiiUserPosition=function(r){"use strict";var C="0.
|
|
1
|
+
var jsPsychTobiiUserPosition=function(r){"use strict";var C="0.2.1";class P{constructor(t,e){this.BOX_WIDTH=400,this.BOX_HEIGHT=300,this.MIN_FACE_SCALE=.4,this.MAX_FACE_SCALE=1.6,this.OPTIMAL_FACE_SCALE=1,this.container=t,this.options=e,this.createDisplay()}createDisplay(){this.container.innerHTML="",this.messageElement=document.createElement("div"),this.messageElement.className="tobii-user-position-message",this.messageElement.textContent=this.options.message,this.container.appendChild(this.messageElement);const t=document.createElement("div");t.className="tobii-user-position-guide",this.container.appendChild(t),this.trackingBoxElement=document.createElement("div"),this.trackingBoxElement.className="tobii-tracking-box",this.trackingBoxElement.setAttribute("role","img"),this.trackingBoxElement.setAttribute("aria-label","Head position tracking display"),this.trackingBoxElement.style.cssText=`
|
|
2
2
|
position: relative;
|
|
3
3
|
width: ${this.BOX_WIDTH}px;
|
|
4
4
|
height: ${this.BOX_HEIGHT}px;
|
|
@@ -138,4 +138,4 @@ var jsPsychTobiiUserPosition=function(r){"use strict";var C="0.1.1";class P{cons
|
|
|
138
138
|
<div class="tobii-user-position-container">
|
|
139
139
|
</div>
|
|
140
140
|
`;const n=t.querySelector(".tobii-user-position-container"),l=new P(n,{message:e.message,showDistanceFeedback:e.show_distance_feedback,showPositionFeedback:e.show_position_feedback,backgroundColor:e.background_color,goodColor:e.good_color,fairColor:e.fair_color,poorColor:e.poor_color,fontSize:e.font_size,positionThresholdGood:e.position_threshold_good,positionThresholdFair:e.position_threshold_fair,distanceThresholdGood:e.distance_threshold_good,distanceThresholdFair:e.distance_threshold_fair});let o=null;e.duration===null&&(o=document.createElement("button"),o.className="tobii-user-position-button",o.textContent=e.button_text,e.require_good_position&&(o.disabled=!0),n.appendChild(o));const a=[],d=performance.now(),S=setInterval(()=>w(this,null,function*(){try{const u=yield s.getUserPosition();l.updatePosition(u);const m=l.getCurrentQuality(u);a.push(m),o&&e.require_good_position&&(o.disabled=!m.isGoodPosition)}catch(u){console.error("Error updating user position:",u)}}),e.update_interval),F=()=>{clearInterval(S),l.destroy(),t.innerHTML="",f.removeStyles()},E=()=>{var u,m,v,T;const p=a.filter(c=>c.averageX!==null&&c.averageY!==null&&c.averageZ!==null);let x=null,_=null,A=null,h=null;p.length>0&&(x=p.reduce((c,g)=>c+g.averageX,0)/p.length,_=p.reduce((c,g)=>c+g.averageY,0)/p.length,A=p.reduce((c,g)=>c+g.averageZ,0)/p.length,h=a[a.length-1]);const I={average_x:x,average_y:_,average_z:A,position_good:(u=h==null?void 0:h.isGoodPosition)!=null?u:!1,horizontal_status:(m=h==null?void 0:h.horizontalStatus)!=null?m:"poor",vertical_status:(v=h==null?void 0:h.verticalStatus)!=null?v:"poor",distance_status:(T=h==null?void 0:h.distanceStatus)!=null?T:"poor",rt:Math.round(performance.now()-d)};F(),this.jsPsych.finishTrial(I),i()};o&&o.addEventListener("click",E),e.duration!=null&&this.jsPsych.pluginAPI.setTimeout(E,e.duration)})}};return b.info=k,b}(jsPsychModule);
|
|
141
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-tobii-user-position@0.
|
|
141
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-tobii-user-position@0.2.1/dist/index.browser.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.1.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":["version","PositionDisplay","container","options","guideContainer","optimalZone","closeLabel","farLabel","optimalMarker","svg","faceOutline","nose","positionData","avgX","avgY","avgZ","left","right","leftValid","rightValid","x","y","z","_positionData","offsetX","offsetY","scale","fillPercent","goodMin","goodMax","fairMin","fairMax","color","leftColor","rightColor","quality","feedback","issues","posFairThreshold","distFairThreshold","xOffset","horizontalStatus","yOffset","verticalStatus","zOffset","distanceStatus","info","ParameterType","_TobiiUserPositionPlugin","jsPsych","el","trial","css","styleElement","display_element","resolve","tobiiExtension","positionDisplay","continueButton","positionSamples","startTime","updateInterval","__async","error","cleanup","endTrial","_a","_b","_c","_d","validSamples","s","averageX","averageY","averageZ","finalQuality","sum","trialData"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.2.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.2.1\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.2.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":["version","PositionDisplay","container","options","guideContainer","optimalZone","closeLabel","farLabel","optimalMarker","svg","faceOutline","nose","positionData","avgX","avgY","avgZ","left","right","leftValid","rightValid","x","y","z","_positionData","offsetX","offsetY","scale","fillPercent","goodMin","goodMax","fairMin","fairMax","color","leftColor","rightColor","quality","feedback","issues","posFairThreshold","distFairThreshold","xOffset","horizontalStatus","yOffset","verticalStatus","zOffset","distanceStatus","info","ParameterType","_TobiiUserPositionPlugin","jsPsych","el","trial","css","styleElement","display_element","resolve","tobiiExtension","positionDisplay","continueButton","positionSamples","startTime","updateInterval","__async","error","cleanup","endTrial","_a","_b","_c","_d","validSamples","s","averageX","averageY","averageZ","finalQuality","sum","trialData"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.1.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.2.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.2.1\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.2.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.1.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.2.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.2.1\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.2.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspsych/plugin-tobii-user-position",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "jsPsych plugin for Tobii eye tracker user position guide",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
"homepage": "https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme",
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"jspsych": ">=8.0.0",
|
|
47
|
-
"@jspsych/extension-tobii": "^0.1
|
|
47
|
+
"@jspsych/extension-tobii": "^0.2.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@jspsych/config": "^3.2.2",
|
|
51
51
|
"@jspsych/test-utils": "^1.0.0",
|
|
52
|
-
"@jspsych/extension-tobii": "^0.
|
|
52
|
+
"@jspsych/extension-tobii": "^0.2.1",
|
|
53
53
|
"jspsych": "^8.0.0"
|
|
54
54
|
}
|
|
55
55
|
}
|