# Numerical Stroop — PsychoJS (Online)

> **Parent**: [psych-experiment-coder](../../SKILL.md) · [jsPsych/JavaScript Index](index.md)
> **Config reference**: [numerical-stroop](../../../psych-experiment-programming/paradigms/numerical-stroop.md)
> **Source**: [Pavlovia demos/numerical_stroop](https://gitlab.pavlovia.org/demos/numerical_stroop) · PsychoJS (PsychoPy online export)
> **Platform note**: This is PsychoJS code (PsychoPy's JavaScript runtime for online experiments), NOT jsPsych library code.

## Experiment Logic

The Numerical Stroop task, based on Henik and Tzelgov (1982), is a variant of the classic Stroop paradigm using numerical stimuli instead of color words. Participants must compare numbers along two dimensions -- physical size and numerical magnitude -- which may be congruent or incongruent.

The experiment has two block types. In physical trials, participants select the physically larger number while ignoring its numerical value. In semantic trials, participants select the numerically larger number while ignoring its physical size. This creates congruent trials (e.g., physically larger 5 vs. smaller 3) and incongruent trials (e.g., physically larger 3 vs. smaller 5).

The experiment is structured with an outer block loop (`blocks`) that presents physical and semantic blocks in counterbalanced order. Each block begins with specific instructions. Practice trials with feedback precede each block type. The condition files (`practice_physical.xlsx`, `stroop_input_physical.xlsx`, `practice_semantic.xlsx`, `stroop_input_semantic.xlsx`) define trial parameters.

Two numbers are presented side by side as `visual.TextStim` elements with varying font sizes. Participants press '1' for the left number or '2' for the right number. RT and accuracy are recorded per trial. The typical finding is longer RT on incongruent vs. congruent trials (the numerical Stroop effect).

## Key Design Patterns

- **Two-dimension Stroop design**: physical size judgment vs. numerical magnitude judgment, creating a double dissociation
- **Block-level counterbalancing**: `blockDefinitions.xlsx` specifies which block type comes first, with dual practice-then-experiment structure for each
- **Four condition files**: separate Excel files for practice-physical, experiment-physical, practice-semantic, and experiment-semantic trials
- **Dynamic text sizing**: `visual.TextStim` with `letterHeight` set per trial to manipulate physical size independently from numerical value
- **Binary choice response**: keys '1' (left) and '2' (right) for selecting which number is larger
- **Block instructions**: specific instruction text for each block type explaining whether to judge physical size or numerical magnitude

## Code Example

```javascript
// Source: numerical_stroop (demos/numerical_stroop)
// Project URL: https://gitlab.pavlovia.org/demos/numerical_stroop
// Original file: NumericalStroop.js
﻿/************************ 
 * Numericalstroop Test *
 ************************/

import { core, data, sound, util, visual, hardware } from './lib/psychojs-2023.1.3.js';
const { PsychoJS } = core;
const { TrialHandler, MultiStairHandler } = data;
const { Scheduler } = util;
//some handy aliases as in the psychopy scripts;
const { abs, sin, cos, PI: pi, sqrt } = Math;
const { round } = util;


// store info about the experiment session:
let expName = 'NumericalStroop';  // from the Builder filename that created this script
let expInfo = {
    'participant': '',
    'session': '001',
};

// Start code blocks for 'Before Experiment'
// init psychoJS:
const psychoJS = new PsychoJS({
  debug: true
});

// open window:
psychoJS.openWindow({
  fullscr: true,
  color: new util.Color([0, 0, 0]),
  units: 'height',
  waitBlanking: true
});
// schedule the experiment:
psychoJS.schedule(psychoJS.gui.DlgFromDict({
  dictionary: expInfo,
  title: expName
}));

const flowScheduler = new Scheduler(psychoJS);
const dialogCancelScheduler = new Scheduler(psychoJS);
psychoJS.scheduleCondition(function() { return (psychoJS.gui.dialogComponent.button === 'OK'); }, flowScheduler, dialogCancelScheduler);

// flowScheduler gets run if the participants presses OK
flowScheduler.add(updateInfo); // add timeStamp
flowScheduler.add(experimentInit);
const blocksLoopScheduler = new Scheduler(psychoJS);
flowScheduler.add(blocksLoopBegin(blocksLoopScheduler));
flowScheduler.add(blocksLoopScheduler);
flowScheduler.add(blocksLoopEnd);
flowScheduler.add(EndRoutineBegin());
flowScheduler.add(EndRoutineEachFrame());
flowScheduler.add(EndRoutineEnd());
flowScheduler.add(quitPsychoJS, '', true);

// quit if user presses Cancel in dialog box:
dialogCancelScheduler.add(quitPsychoJS, '', false);

psychoJS.start({
  expName: expName,
  expInfo: expInfo,
  resources: [
    // resources:
    {'name': 'blockDefinitions.xlsx', 'path': 'blockDefinitions.xlsx'},
    {'name': 'practice_physical.xlsx', 'path': 'practice_physical.xlsx'},
    {'name': 'stroop_input_physical.xlsx', 'path': 'stroop_input_physical.xlsx'},
    {'name': 'practice_semantic.xlsx', 'path': 'practice_semantic.xlsx'},
    {'name': 'stroop_input_semantic.xlsx', 'path': 'stroop_input_semantic.xlsx'},
  ]
});

psychoJS.experimentLogger.setLevel(core.Logger.ServerLevel.EXP);


var currentLoop;
var frameDur;
async function updateInfo() {
  currentLoop = psychoJS.experiment;  // right now there are no loops
  expInfo['date'] = util.MonotonicClock.getDateStr();  // add a simple timestamp
  expInfo['expName'] = expName;
  expInfo['psychopyVersion'] = '2023.1.3';
  expInfo['OS'] = window.navigator.platform;


  // store frame rate of monitor if we can measure it successfully
  expInfo['frameRate'] = psychoJS.window.getActualFrameRate();
  if (typeof expInfo['frameRate'] !== 'undefined')
    frameDur = 1.0 / Math.round(expInfo['frameRate']);
  else
    frameDur = 1.0 / 60.0; // couldn't get a reliable measure so guess

  // add info from the URL:
  util.addInfoFromUrl(expInfo);
  

  
  psychoJS.experiment.dataFileName = (("." + "/") + `data/${expInfo["participant"]}_${expName}_${expInfo["date"]}`);


  return Scheduler.Event.NEXT;
}


var BlockInstructClock;
var BlockInstrText;
var Key_resp_2;
var TrialClock;
var Fixation;
var NumberLeft;
var NumberRight;
var Response;
var FeedbackClock;
var msg;
var FeedbackMessage;
var EndOfPracticeClock;
var Text;
var Key_resp_3;
var EndClock;
var Text5;
var globalClock;
var routineTimer;
async function experimentInit() {
  // Initialize components for Routine "BlockInstruct"
  BlockInstructClock = new util.Clock();
  BlockInstrText = new visual.TextStim({
    win: psychoJS.window,
    name: 'BlockInstrText',
    text: '',
    font: 'Arial',
    units: undefined, 
    pos: [0, 0], height: 0.07,  wrapWidth: undefined, ori: 0,
    languageStyle: 'LTR',
    color: new util.Color('white'),  opacity: 1,
    depth: 0.0 
  });
  
  Key_resp_2 = new core.Keyboard({psychoJS: psychoJS, clock: new util.Clock(), waitForStart: true});
  
  // Initialize components for Routine "Trial"
  TrialClock = new util.Clock();
  Fixation = new visual.TextStim({
    win: psychoJS.window,
    name: 'Fixation',
    text: '+',
    font: 'Arial',
    units: undefined, 
    pos: [0, 0], height: 0.07,  wrapWidth: undefined, ori: 0,
    languageStyle: 'LTR',
    color: new util.Color('white'),  opacity: 1,
    depth: 0.0 
  });
  
  NumberLeft = new visual.TextStim({
    win: psychoJS.window,
    name: 'NumberLeft',
    text: '',
    font: 'Arial',
    units: undefined, 
    pos: [(- 0.075), 0], height: 1.0,  wrapWidth: undefined, ori: 0,
    languageStyle: 'LTR',
    color: new util.Color('white'),  opacity: 1,
    depth: -1.0 
  });
  
  NumberRight = new visual.TextStim({
    win: psychoJS.window,
    name: 'NumberRight',
    text: '',
    font: 'Arial',
    units: undefined, 
    pos: [0.075, 0], height: 1.0,  wrapWidth: undefined, ori: 0,
    languageStyle: 'LTR',
    color: new util.Color('white'),  opacity: 1,
    depth: -2.0 
  });
  
  Response = new core.Keyboard({psychoJS: psychoJS, clock: new util.Clock(), waitForStart: true});
  
  // Initialize components for Routine "Feedback"
  FeedbackClock = new util.Clock();
  // Run 'Begin Experiment' code from Code
  msg='hello'
  FeedbackMessage = new visual.TextStim({
    win: psychoJS.window,
    name: 'FeedbackMessage',
    text: '',
    font: 'Arial',
    units: undefined, 
    pos: [0, 0], height: 0.07,  wrapWidth: undefined, ori: 0,
    languageStyle: 'LTR',
    color: new util.Color('black'),  opacity: 1,
    depth: -1.0 
  });
  
  // Initialize components for Routine "EndOfPractice"
  EndOfPracticeClock = new util.Clock();
  Text = new visual.TextStim({
    win: psychoJS.window,
    name: 'Text',
    text: 'This is the end of practice trials.\nPress the spacebar when ready to start the main trials',
    font: 'Arial',
    units: undefined, 
    pos: [0, 0], height: 0.07,  wrapWidth: undefined, ori: 0,
    languageStyle: 'LTR',
    color: new util.Color('white'),  opacity: 1,
    depth: 0.0 
  });
  
  Key_resp_3 = new core.Keyboard({psychoJS: psychoJS, clock: new util.Clock(), waitForStart: true});
  
  // Initialize components for Routine "End"
  EndClock = new util.Clock();
  Text5 = new visual.TextStim({
    win: psychoJS.window,
    name: 'Text5',
    text: 'This is the end of the experiment.\nThank you for your participation!',
    font: 'Arial',
    units: undefined, 
    pos: [0, 0], height: 0.1,  wrapWidth: undefined, ori: 0,
    languageStyle: 'LTR',
    color: new util.Color('white'),  opacity: 1,
    depth: 0.0 
  });
  
  // Create some handy timers
  globalClock = new util.Clock();  // to track the time since experiment started
  routineTimer = new util.CountdownTimer();  // to track time remaining of each (non-slip) routine
  
  return Scheduler.Event.NEXT;
}


var blocks;
function blocksLoopBegin(blocksLoopScheduler, snapshot) {
  return async function() {
    TrialHandler.fromSnapshot(snapshot); // update internal variables (.thisN etc) of the loop
    
    // set up handler to look after randomisation of conditions etc
    blocks = new TrialHandler({
      psychoJS: psychoJS,
      nReps: 1, method: TrialHandler.Method.RANDOM,
      extraInfo: expInfo, originPath: undefined,
      trialList: 'blockDefinitions.xlsx',
      seed: undefined, name: 'blocks'
    });
    psychoJS.experiment.addLoop(blocks); // add the loop to the experiment
    currentLoop = blocks;  // we're now the current loop
    
    // Schedule all the trials in the trialList:
    for (const thisBlock of blocks) {
      snapshot = blocks.getSnapshot();
      blocksLoopScheduler.add(importConditions(snapshot));
      blocksLoopScheduler.add(BlockInstructRoutineBegin(snapshot));
      blocksLoopScheduler.add(BlockInstructRoutineEachFrame());
      blocksLoopScheduler.add(BlockInstructRoutineEnd(snapshot));
      const ftrialsLoopScheduler = new Scheduler(psychoJS);
      blocksLoopScheduler.add(ftrialsLoopBegin(ftrialsLoopScheduler, snapshot));
      blocksLoopScheduler.add(ftrialsLoopScheduler);
      blocksLoopScheduler.add(ftrialsLoopEnd);
      blocksLoopScheduler.add(EndOfPracticeRoutineBegin(snapshot));
      blocksLoopScheduler.add(EndOfPracticeRoutineEachFrame());
      blocksLoopScheduler.add(EndOfPracticeRoutineEnd(snapshot));
      const trialsLoopScheduler = new Scheduler(psychoJS);
      blocksLoopScheduler.add(trialsLoopBegin(trialsLoopScheduler, snapshot));
      blocksLoopScheduler.add(trialsLoopScheduler);
      blocksLoopScheduler.add(trialsLoopEnd);
      blocksLoopScheduler.add(blocksLoopEndIteration(blocksLoopScheduler, snapshot));
    }
    
    return Scheduler.Event.NEXT;
  }
}


var ftrials;
function ftrialsLoopBegin(ftrialsLoopScheduler, snapshot) {
  return async function() {
    TrialHandler.fromSnapshot(snapshot); // update internal variables (.thisN etc) of the loop
    
    // set up handler to look after randomisation of conditions etc
    ftrials = new TrialHandler({
      psychoJS: psychoJS,
      nReps: 1, method: TrialHandler.Method.RANDOM,
      extraInfo: expInfo, originPath: undefined,
      trialList: practiceFile,
      seed: undefined, name: 'ftrials'
    });
    psychoJS.experiment.addLoop(ftrials); // add the loop to the experiment
    currentLoop = ftrials;  // we're now the current loop
    
    // Schedule all the trials in the trialList:
    for (const thisFtrial of ftrials) {
      snapshot = ftrials.getSnapshot();
      ftrialsLoopScheduler.add(importConditions(snapshot));
      ftrialsLoopScheduler.add(TrialRoutineBegin(snapshot));
      ftrialsLoopScheduler.add(TrialRoutineEachFrame());
      ftrialsLoopScheduler.add(TrialRoutineEnd(snapshot));
      ftrialsLoopScheduler.add(FeedbackRoutineBegin(snapshot));
      ftrialsLoopScheduler.add(FeedbackRoutineEachFrame());
      ftrialsLoopScheduler.add(FeedbackRoutineEnd(snapshot));
      ftrialsLoopScheduler.add(ftrialsLoopEndIteration(ftrialsLoopScheduler, snapshot));
    }
    
    return Scheduler.Event.NEXT;
  }
}


async function ftrialsLoopEnd() {
  // terminate loop
  psychoJS.experiment.removeLoop(ftrials);
  // update the current loop from the ExperimentHandler
  if (psychoJS.experiment._unfinishedLoops.length>0)
    currentLoop = psychoJS.experiment._unfinishedLoops.at(-1);
  else
    currentLoop = psychoJS.experiment;  // so we use addData from the experiment
  return Scheduler.Event.NEXT;
}


function ftrialsLoopEndIteration(scheduler, snapshot) {
  // ------Prepare for next entry------
  return async function () {
    if (typeof snapshot !== 'undefined') {
      // ------Check if user ended loop early------
      if (snapshot.finished) {
        // Check for and save orphaned data
        if (psychoJS.experiment.isEntryEmpty()) {
          psychoJS.experiment.nextEntry(snapshot);
        }
        scheduler.stop();
      } else {
        psychoJS.experiment.nextEntry(snapshot);
      }
    return Scheduler.Event.NEXT;
    }
  };
}


var trials;
function trialsLoopBegin(trialsLoopScheduler, snapshot) {
  return async function() {
    TrialHandler.fromSnapshot(snapshot); // update internal variables (.thisN etc) of the loop
    
    // set up handler to look after randomisation of conditions etc
    trials = new TrialHandler({
      psychoJS: psychoJS,
      nReps: 1, method: TrialHandler.Method.RANDOM,
      extraInfo: expInfo, originPath: undefined,
      trialList: conditionsFile,
      seed: undefined, name: 'trials'
    });
    psychoJS.experiment.addLoop(trials); // add the loop to the experiment
    currentLoop = trials;  // we're now the current loop
    
    // Schedule all the trials in the trialList:
    for (const thisTrial of trials) {
      snapshot = trials.getSnapshot();
      trialsLoopScheduler.add(importConditions(snapshot));
      trialsLoopScheduler.add(TrialRoutineBegin(snapshot));
      trialsLoopScheduler.add(TrialRoutineEachFrame());
      trialsLoopScheduler.add(TrialRoutineEnd(snapshot));
      trialsLoopScheduler.add(trialsLoopEndIteration(trialsLoopScheduler, snapshot));
    }
    
    return Scheduler.Event.NEXT;
  }
}


async function trialsLoopEnd() {
  // terminate loop
  psychoJS.experiment.removeLoop(trials);
  // update the current loop from the ExperimentHandler
  if (psychoJS.experiment._unfinishedLoops.length>0)
    currentLoop = psychoJS.experiment._unfinishedLoops.at(-1);
  else
    currentLoop = psychoJS.experiment;  // so we use addData from the experiment
  return Scheduler.Event.NEXT;
}


function trialsLoopEndIteration(scheduler, snapshot) {
  // ------Prepare for next entry------
  return async function () {
    if (typeof snapshot !== 'undefined') {
      // ------Check if user ended loop early------
      if (snapshot.finished) {
        // Check for and save orphaned data
        if (psychoJS.experiment.isEntryEmpty()) {
          psychoJS.experiment.nextEntry(snapshot);
        }
        scheduler.stop();
      } else {
        psychoJS.experiment.nextEntry(snapshot);
      }
    return Scheduler.Event.NEXT;
    }
  };
}


async function blocksLoopEnd() {
  // terminate loop
  psychoJS.experiment.removeLoop(blocks);
  // update the current loop from the ExperimentHandler
  if (psychoJS.experiment._unfinishedLoops.length>0)
    currentLoop = psychoJS.experiment._unfinishedLoops.at(-1);
  else
    currentLoop = psychoJS.experiment;  // so we use addData from the experiment
  return Scheduler.Event.NEXT;
}


function blocksLoopEndIteration(scheduler, snapshot) {
  // ------Prepare for next entry------
  return async function () {
    if (typeof snapshot !== 'undefined') {
      // ------Check if user ended loop early------
      if (snapshot.finished) {
        // Check for and save orphaned data
        if (psychoJS.experiment.isEntryEmpty()) {
          psychoJS.experiment.nextEntry(snapshot);
        }
        scheduler.stop();
      }
    return Scheduler.Event.NEXT;
    }
  };
}


var t;
var frameN;
var continueRoutine;
var _Key_resp_2_allKeys;
var BlockInstructComponents;
function BlockInstructRoutineBegin(snapshot) {
  return async function () {
    TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
    
    //--- Prepare to start Routine 'BlockInstruct' ---
    t = 0;
    BlockInstructClock.reset(); // clock
    frameN = -1;
    continueRoutine = true; // until we're told otherwise
    // update component parameters for each repeat
    BlockInstrText.setText(instructionText);
    Key_resp_2.keys = undefined;
    Key_resp_2.rt = undefined;
    _Key_resp_2_allKeys = [];
    // keep track of which components have finished
    BlockInstructComponents = [];
    BlockInstructComponents.push(BlockInstrText);
    BlockInstructComponents.push(Key_resp_2);
    
    for (const thisComponent of BlockInstructComponents)
      if ('status' in thisComponent)
        thisComponent.status = PsychoJS.Status.NOT_STARTED;
    return Scheduler.Event.NEXT;
  }
}


function BlockInstructRoutineEachFrame() {
  return async function () {
    //--- Loop for each frame of Routine 'BlockInstruct' ---
    // get current time
    t = BlockInstructClock.getTime();
    frameN = frameN + 1;// number of completed frames (so 0 is the first frame)
    // update/draw components on each frame
    
    // *BlockInstrText* updates
    if (t >= 0.0 && BlockInstrText.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      BlockInstrText.tStart = t;  // (not accounting for frame time here)
      BlockInstrText.frameNStart = frameN;  // exact frame index
      
      BlockInstrText.setAutoDraw(true);
    }

    
    // *Key_resp_2* updates
    if (t >= 0.0 && Key_resp_2.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      Key_resp_2.tStart = t;  // (not accounting for frame time here)
      Key_resp_2.frameNStart = frameN;  // exact frame index
      
      // keyboard checking is just starting
      psychoJS.window.callOnFlip(function() { Key_resp_2.clock.reset(); });  // t=0 on next screen flip
      psychoJS.window.callOnFlip(function() { Key_resp_2.start(); }); // start on screen flip
      psychoJS.window.callOnFlip(function() { Key_resp_2.clearEvents(); });
    }

    if (Key_resp_2.status === PsychoJS.Status.STARTED) {
      let theseKeys = Key_resp_2.getKeys({keyList: ['space'], waitRelease: false});
      _Key_resp_2_allKeys = _Key_resp_2_allKeys.concat(theseKeys);
      if (_Key_resp_2_allKeys.length > 0) {
        Key_resp_2.keys = _Key_resp_2_allKeys[_Key_resp_2_allKeys.length - 1].name;  // just the last key pressed
        Key_resp_2.rt = _Key_resp_2_allKeys[_Key_resp_2_allKeys.length - 1].rt;
        Key_resp_2.duration = _Key_resp_2_allKeys[_Key_resp_2_allKeys.length - 1].duration;
        // a response ends the routine
        continueRoutine = false;
      }
    }
    
    // check for quit (typically the Esc key)
    if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) {
      return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false);
    }
    
    // check if the Routine should terminate
    if (!continueRoutine) {  // a component has requested a forced-end of Routine
      return Scheduler.Event.NEXT;
    }
    
    continueRoutine = false;  // reverts to True if at least one component still running
    for (const thisComponent of BlockInstructComponents)
      if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) {
        continueRoutine = true;
        break;
      }
    
    // refresh the screen if continuing
    if (continueRoutine) {
      return Scheduler.Event.FLIP_REPEAT;
    } else {
      return Scheduler.Event.NEXT;
    }
  };
}


function BlockInstructRoutineEnd(snapshot) {
  return async function () {
    //--- Ending Routine 'BlockInstruct' ---
    for (const thisComponent of BlockInstructComponents) {
      if (typeof thisComponent.setAutoDraw === 'function') {
        thisComponent.setAutoDraw(false);
      }
    }
    // update the trial handler
    if (currentLoop instanceof MultiStairHandler) {
      currentLoop.addResponse(Key_resp_2.corr, level);
    }
    psychoJS.experiment.addData('Key_resp_2.keys', Key_resp_2.keys);
    if (typeof Key_resp_2.keys !== 'undefined') {  // we had a response
        psychoJS.experiment.addData('Key_resp_2.rt', Key_resp_2.rt);
        psychoJS.experiment.addData('Key_resp_2.duration', Key_resp_2.duration);
        routineTimer.reset();
        }
    
    Key_resp_2.stop();
    // the Routine "BlockInstruct" was not non-slip safe, so reset the non-slip timer
    routineTimer.reset();
    
    // Routines running outside a loop should always advance the datafile row
    if (currentLoop === psychoJS.experiment) {
      psychoJS.experiment.nextEntry(snapshot);
    }
    return Scheduler.Event.NEXT;
  }
}


var _Response_allKeys;
var TrialComponents;
function TrialRoutineBegin(snapshot) {
  return async function () {
    TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
    
    //--- Prepare to start Routine 'Trial' ---
    t = 0;
    TrialClock.reset(); // clock
    frameN = -1;
    continueRoutine = true; // until we're told otherwise
    // update component parameters for each repeat
    NumberLeft.setText(number1);
    NumberLeft.setHeight(size1);
    NumberRight.setText(number2);
    NumberRight.setHeight(size2);
    Response.keys = undefined;
    Response.rt = undefined;
    _Response_allKeys = [];
    // keep track of which components have finished
    TrialComponents = [];
    TrialComponents.push(Fixation);
    TrialComponents.push(NumberLeft);
    TrialComponents.push(NumberRight);
    TrialComponents.push(Response);
    
    for (const thisComponent of TrialComponents)
      if ('status' in thisComponent)
        thisComponent.status = PsychoJS.Status.NOT_STARTED;
    return Scheduler.Event.NEXT;
  }
}


var frameRemains;
function TrialRoutineEachFrame() {
  return async function () {
    //--- Loop for each frame of Routine 'Trial' ---
    // get current time
    t = TrialClock.getTime();
    frameN = frameN + 1;// number of completed frames (so 0 is the first frame)
    // update/draw components on each frame
    
    // *Fixation* updates
    if (t >= 0.0 && Fixation.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      Fixation.tStart = t;  // (not accounting for frame time here)
      Fixation.frameNStart = frameN;  // exact frame index
      
      Fixation.setAutoDraw(true);
    }

    frameRemains = 0.0 + 0.1 - psychoJS.window.monitorFramePeriod * 0.75;  // most of one frame period left
    if (Fixation.status === PsychoJS.Status.STARTED && t >= frameRemains) {
      Fixation.setAutoDraw(false);
    }
    
    // *NumberLeft* updates
    if (t >= 0.2 && NumberLeft.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      NumberLeft.tStart = t;  // (not accounting for frame time here)
      NumberLeft.frameNStart = frameN;  // exact frame index
      
      NumberLeft.setAutoDraw(true);
    }

    
    // *NumberRight* updates
    if (t >= 0.2 && NumberRight.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      NumberRight.tStart = t;  // (not accounting for frame time here)
      NumberRight.frameNStart = frameN;  // exact frame index
      
      NumberRight.setAutoDraw(true);
    }

    
    // *Response* updates
    if (t >= 0.2 && Response.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      Response.tStart = t;  // (not accounting for frame time here)
      Response.frameNStart = frameN;  // exact frame index
      
      // keyboard checking is just starting
      psychoJS.window.callOnFlip(function() { Response.clock.reset(); });  // t=0 on next screen flip
      psychoJS.window.callOnFlip(function() { Response.start(); }); // start on screen flip
    }

    if (Response.status === PsychoJS.Status.STARTED) {
      let theseKeys = Response.getKeys({keyList: ['a', 'k'], waitRelease: false});
      _Response_allKeys = _Response_allKeys.concat(theseKeys);
      if (_Response_allKeys.length > 0) {
        Response.keys = _Response_allKeys[_Response_allKeys.length - 1].name;  // just the last key pressed
        Response.rt = _Response_allKeys[_Response_allKeys.length - 1].rt;
        Response.duration = _Response_allKeys[_Response_allKeys.length - 1].duration;
        // was this correct?
        if (Response.keys == corrAns) {
            Response.corr = 1;
        } else {
            Response.corr = 0;
        }
        // a response ends the routine
        continueRoutine = false;
      }
    }
    
    // check for quit (typically the Esc key)
    if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) {
      return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false);
    }
    
    // check if the Routine should terminate
    if (!continueRoutine) {  // a component has requested a forced-end of Routine
      return Scheduler.Event.NEXT;
    }
    
    continueRoutine = false;  // reverts to True if at least one component still running
    for (const thisComponent of TrialComponents)
      if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) {
        continueRoutine = true;
        break;
      }
    
    // refresh the screen if continuing
    if (continueRoutine) {
      return Scheduler.Event.FLIP_REPEAT;
    } else {
      return Scheduler.Event.NEXT;
    }
  };
}


function TrialRoutineEnd(snapshot) {
  return async function () {
    //--- Ending Routine 'Trial' ---
    for (const thisComponent of TrialComponents) {
      if (typeof thisComponent.setAutoDraw === 'function') {
        thisComponent.setAutoDraw(false);
      }
    }
    // was no response the correct answer?!
    if (Response.keys === undefined) {
      if (['None','none',undefined].includes(corrAns)) {
         Response.corr = 1;  // correct non-response
      } else {
         Response.corr = 0;  // failed to respond (incorrectly)
      }
    }
    // store data for current loop
    // update the trial handler
    if (currentLoop instanceof MultiStairHandler) {
      currentLoop.addResponse(Response.corr, level);
    }
    psychoJS.experiment.addData('Response.keys', Response.keys);
    psychoJS.experiment.addData('Response.corr', Response.corr);
    if (typeof Response.keys !== 'undefined') {  // we had a response
        psychoJS.experiment.addData('Response.rt', Response.rt);
        psychoJS.experiment.addData('Response.duration', Response.duration);
        routineTimer.reset();
        }
    
    Response.stop();
    // the Routine "Trial" was not non-slip safe, so reset the non-slip timer
    routineTimer.reset();
    
    // Routines running outside a loop should always advance the datafile row
    if (currentLoop === psychoJS.experiment) {
      psychoJS.experiment.nextEntry(snapshot);
    }
    return Scheduler.Event.NEXT;
  }
}


var FeedbackComponents;
function FeedbackRoutineBegin(snapshot) {
  return async function () {
    TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
    
    //--- Prepare to start Routine 'Feedback' ---
    t = 0;
    FeedbackClock.reset(); // clock
    frameN = -1;
    continueRoutine = true; // until we're told otherwise
    routineTimer.add(1.000000);
    // update component parameters for each repeat
    // Run 'Begin Routine' code from Code
    if (Response.corr) { //stored on last run routine
      msg = "Correct!"; 
    } else {
      msg = "Oops! That was wrong"
    }
    FeedbackMessage.setText(msg);
    // keep track of which components have finished
    FeedbackComponents = [];
    FeedbackComponents.push(FeedbackMessage);
    
    for (const thisComponent of FeedbackComponents)
      if ('status' in thisComponent)
        thisComponent.status = PsychoJS.Status.NOT_STARTED;
    return Scheduler.Event.NEXT;
  }
}


function FeedbackRoutineEachFrame() {
  return async function () {
    //--- Loop for each frame of Routine 'Feedback' ---
    // get current time
    t = FeedbackClock.getTime();
    frameN = frameN + 1;// number of completed frames (so 0 is the first frame)
    // update/draw components on each frame
    
    // *FeedbackMessage* updates
    if (t >= 0.0 && FeedbackMessage.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      FeedbackMessage.tStart = t;  // (not accounting for frame time here)
      FeedbackMessage.frameNStart = frameN;  // exact frame index
      
      FeedbackMessage.setAutoDraw(true);
    }

    frameRemains = 0.0 + 1 - psychoJS.window.monitorFramePeriod * 0.75;  // most of one frame period left
    if (FeedbackMessage.status === PsychoJS.Status.STARTED && t >= frameRemains) {
      FeedbackMessage.setAutoDraw(false);
    }
    // check for quit (typically the Esc key)
    if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) {
      return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false);
    }
    
    // check if the Routine should terminate
    if (!continueRoutine) {  // a component has requested a forced-end of Routine
      return Scheduler.Event.NEXT;
    }
    
    continueRoutine = false;  // reverts to True if at least one component still running
    for (const thisComponent of FeedbackComponents)
      if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) {
        continueRoutine = true;
        break;
      }
    
    // refresh the screen if continuing
    if (continueRoutine && routineTimer.getTime() > 0) {
      return Scheduler.Event.FLIP_REPEAT;
    } else {
      return Scheduler.Event.NEXT;
    }
  };
}


function FeedbackRoutineEnd(snapshot) {
  return async function () {
    //--- Ending Routine 'Feedback' ---
    for (const thisComponent of FeedbackComponents) {
      if (typeof thisComponent.setAutoDraw === 'function') {
        thisComponent.setAutoDraw(false);
      }
    }
    // Routines running outside a loop should always advance the datafile row
    if (currentLoop === psychoJS.experiment) {
      psychoJS.experiment.nextEntry(snapshot);
    }
    return Scheduler.Event.NEXT;
  }
}


var _Key_resp_3_allKeys;
var EndOfPracticeComponents;
function EndOfPracticeRoutineBegin(snapshot) {
  return async function () {
    TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
    
    //--- Prepare to start Routine 'EndOfPractice' ---
    t = 0;
    EndOfPracticeClock.reset(); // clock
    frameN = -1;
    continueRoutine = true; // until we're told otherwise
    // update component parameters for each repeat
    Key_resp_3.keys = undefined;
    Key_resp_3.rt = undefined;
    _Key_resp_3_allKeys = [];
    // keep track of which components have finished
    EndOfPracticeComponents = [];
    EndOfPracticeComponents.push(Text);
    EndOfPracticeComponents.push(Key_resp_3);
    
    for (const thisComponent of EndOfPracticeComponents)
      if ('status' in thisComponent)
        thisComponent.status = PsychoJS.Status.NOT_STARTED;
    return Scheduler.Event.NEXT;
  }
}


function EndOfPracticeRoutineEachFrame() {
  return async function () {
    //--- Loop for each frame of Routine 'EndOfPractice' ---
    // get current time
    t = EndOfPracticeClock.getTime();
    frameN = frameN + 1;// number of completed frames (so 0 is the first frame)
    // update/draw components on each frame
    
    // *Text* updates
    if (t >= 0.0 && Text.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      Text.tStart = t;  // (not accounting for frame time here)
      Text.frameNStart = frameN;  // exact frame index
      
      Text.setAutoDraw(true);
    }

    
    // *Key_resp_3* updates
    if (t >= 0.0 && Key_resp_3.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      Key_resp_3.tStart = t;  // (not accounting for frame time here)
      Key_resp_3.frameNStart = frameN;  // exact frame index
      
      // keyboard checking is just starting
      psychoJS.window.callOnFlip(function() { Key_resp_3.clock.reset(); });  // t=0 on next screen flip
      psychoJS.window.callOnFlip(function() { Key_resp_3.start(); }); // start on screen flip
      psychoJS.window.callOnFlip(function() { Key_resp_3.clearEvents(); });
    }

    if (Key_resp_3.status === PsychoJS.Status.STARTED) {
      let theseKeys = Key_resp_3.getKeys({keyList: ['space'], waitRelease: false});
      _Key_resp_3_allKeys = _Key_resp_3_allKeys.concat(theseKeys);
      if (_Key_resp_3_allKeys.length > 0) {
        Key_resp_3.keys = _Key_resp_3_allKeys[_Key_resp_3_allKeys.length - 1].name;  // just the last key pressed
        Key_resp_3.rt = _Key_resp_3_allKeys[_Key_resp_3_allKeys.length - 1].rt;
        Key_resp_3.duration = _Key_resp_3_allKeys[_Key_resp_3_allKeys.length - 1].duration;
        // a response ends the routine
        continueRoutine = false;
      }
    }
    
    // check for quit (typically the Esc key)
    if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) {
      return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false);
    }
    
    // check if the Routine should terminate
    if (!continueRoutine) {  // a component has requested a forced-end of Routine
      return Scheduler.Event.NEXT;
    }
    
    continueRoutine = false;  // reverts to True if at least one component still running
    for (const thisComponent of EndOfPracticeComponents)
      if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) {
        continueRoutine = true;
        break;
      }
    
    // refresh the screen if continuing
    if (continueRoutine) {
      return Scheduler.Event.FLIP_REPEAT;
    } else {
      return Scheduler.Event.NEXT;
    }
  };
}


function EndOfPracticeRoutineEnd(snapshot) {
  return async function () {
    //--- Ending Routine 'EndOfPractice' ---
    for (const thisComponent of EndOfPracticeComponents) {
      if (typeof thisComponent.setAutoDraw === 'function') {
        thisComponent.setAutoDraw(false);
      }
    }
    // update the trial handler
    if (currentLoop instanceof MultiStairHandler) {
      currentLoop.addResponse(Key_resp_3.corr, level);
    }
    psychoJS.experiment.addData('Key_resp_3.keys', Key_resp_3.keys);
    if (typeof Key_resp_3.keys !== 'undefined') {  // we had a response
        psychoJS.experiment.addData('Key_resp_3.rt', Key_resp_3.rt);
        psychoJS.experiment.addData('Key_resp_3.duration', Key_resp_3.duration);
        routineTimer.reset();
        }
    
    Key_resp_3.stop();
    // the Routine "EndOfPractice" was not non-slip safe, so reset the non-slip timer
    routineTimer.reset();
    
    // Routines running outside a loop should always advance the datafile row
    if (currentLoop === psychoJS.experiment) {
      psychoJS.experiment.nextEntry(snapshot);
    }
    return Scheduler.Event.NEXT;
  }
}


var EndComponents;
function EndRoutineBegin(snapshot) {
  return async function () {
    TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
    
    //--- Prepare to start Routine 'End' ---
    t = 0;
    EndClock.reset(); // clock
    frameN = -1;
    continueRoutine = true; // until we're told otherwise
    routineTimer.add(1.000000);
    // update component parameters for each repeat
    // keep track of which components have finished
    EndComponents = [];
    EndComponents.push(Text5);
    
    for (const thisComponent of EndComponents)
      if ('status' in thisComponent)
        thisComponent.status = PsychoJS.Status.NOT_STARTED;
    return Scheduler.Event.NEXT;
  }
}


function EndRoutineEachFrame() {
  return async function () {
    //--- Loop for each frame of Routine 'End' ---
    // get current time
    t = EndClock.getTime();
    frameN = frameN + 1;// number of completed frames (so 0 is the first frame)
    // update/draw components on each frame
    
    // *Text5* updates
    if (t >= 0.0 && Text5.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      Text5.tStart = t;  // (not accounting for frame time here)
      Text5.frameNStart = frameN;  // exact frame index
      
      Text5.setAutoDraw(true);
    }

    frameRemains = 0.0 + 1.0 - psychoJS.window.monitorFramePeriod * 0.75;  // most of one frame period left
    if (Text5.status === PsychoJS.Status.STARTED && t >= frameRemains) {
      Text5.setAutoDraw(false);
    }
    // check for quit (typically the Esc key)
    if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) {
      return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false);
    }
    
    // check if the Routine should terminate
    if (!continueRoutine) {  // a component has requested a forced-end of Routine
      return Scheduler.Event.NEXT;
    }
    
    continueRoutine = false;  // reverts to True if at least one component still running
    for (const thisComponent of EndComponents)
      if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) {
        continueRoutine = true;
        break;
      }
    
    // refresh the screen if continuing
    if (continueRoutine && routineTimer.getTime() > 0) {
      return Scheduler.Event.FLIP_REPEAT;
    } else {
      return Scheduler.Event.NEXT;
    }
  };
}


function EndRoutineEnd(snapshot) {
  return async function () {
    //--- Ending Routine 'End' ---
    for (const thisComponent of EndComponents) {
      if (typeof thisComponent.setAutoDraw === 'function') {
        thisComponent.setAutoDraw(false);
      }
    }
    // Routines running outside a loop should always advance the datafile row
    if (currentLoop === psychoJS.experiment) {
      psychoJS.experiment.nextEntry(snapshot);
    }
    return Scheduler.Event.NEXT;
  }
}


function importConditions(currentLoop) {
  return async function () {
    psychoJS.importAttributes(currentLoop.getCurrentTrial());
    return Scheduler.Event.NEXT;
    };
}


async function quitPsychoJS(message, isCompleted) {
  // Check for and save orphaned data
  if (psychoJS.experiment.isEntryEmpty()) {
    psychoJS.experiment.nextEntry();
  }
  
  
  psychoJS.window.close();
  psychoJS.quit({message: message, isCompleted: isCompleted});
  
  return Scheduler.Event.QUIT;
}

```
