init
BIN
images/blue/instructions_1.png
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
images/blue/instructions_2.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
images/blue/instructions_3.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
images/blue/instructions_4.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
images/instruction_images.afphoto
Normal file
BIN
images/red/instructions_1.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
images/red/instructions_2.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
images/red/instructions_3.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
images/red/instructions_4.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
337
index.js
@@ -1,11 +1,31 @@
|
||||
import { initJsPsych } from "jspsych";
|
||||
import "jspsych/css/jspsych.css";
|
||||
import "./styles.css";
|
||||
import { delayed_redirect } from "./utils/helpers.js";
|
||||
import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
|
||||
import { textStimuli } from './scripts/text_stimuli';
|
||||
import { initJsPsych } from 'jspsych';
|
||||
import 'jspsych/css/jspsych.css';
|
||||
import jsPsychHtmlKeyboardResponse from '@jspsych/plugin-html-keyboard-response';
|
||||
import generateUniqueUsernames from './scripts/name-gen.js';
|
||||
import jsPsychFullscreen from '@jspsych/plugin-fullscreen';
|
||||
import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response';
|
||||
import jsPsychLobby from './scripts/plugin-lobby.js';
|
||||
import jsPsychSurvey from '@jspsych/plugin-survey';
|
||||
import '@jspsych/plugin-survey/css/survey.css';
|
||||
import './styles.css';
|
||||
import { getStimulusMap } from './scripts/text-stimuli.js';
|
||||
import jsPsychObjectMoving from './scripts/plugin-object-moving.js';
|
||||
|
||||
const debug = import.meta.env.VITE_DEBUG;
|
||||
const debug = import.meta.env.VITE_DEBUG === 'true';
|
||||
const total_participants = import.meta.env.VITE_TOTAL_PARTICIPANTS;
|
||||
const uniqueUsernames = generateUniqueUsernames(total_participants);
|
||||
const experiment_name = import.meta.env.VITE_EXPERIMENT_NAME;
|
||||
|
||||
let prolific_id;
|
||||
let probe_condition; // P in the params, O = open, S = suspicion mentioned
|
||||
let mapping; // M in the params, TB = together blue, AB = alone blue
|
||||
let short_version = false;
|
||||
|
||||
function delayed_redirect(url) {
|
||||
setTimeout(() => {
|
||||
window.location = url;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
const jsPsych = initJsPsych({
|
||||
on_finish: function() {
|
||||
@@ -21,12 +41,309 @@ const jsPsych = initJsPsych({
|
||||
},
|
||||
});
|
||||
|
||||
const demo_trial = {
|
||||
prolific_id = jsPsych.data.getURLVariable('PROLIFIC_PID');
|
||||
mapping = jsPsych.data.getURLVariable('M');
|
||||
short_version = jsPsych.data.getURLVariable('S') === 'true';
|
||||
const together_colour = mapping === 'TB' ? 'blue' : 'red';
|
||||
const stimulusMap = getStimulusMap(together_colour);
|
||||
|
||||
probe_condition = jsPsych.data.getURLVariable('P');
|
||||
let probe_text;
|
||||
if (probe_condition === 'O') {
|
||||
probe_text =
|
||||
'Did you have any thoughts or observations about the experiment?';
|
||||
} else if (probe_condition === 'S') {
|
||||
probe_text =
|
||||
'Did you have any thoughts, observations, or suspicions about the experiment?';
|
||||
} else if (probe_condition === 'D') {
|
||||
probe_text =
|
||||
'Many studies use deception to create the appearance that you are interacting with a real person. Did you suspect that you were not interacting with a real person?';
|
||||
} else if (probe_condition === 'R') {
|
||||
probe_text =
|
||||
'Our study used deception to create the appearance that you are interacting with a real person. Did you suspect that you were not interacting with a real person?';
|
||||
} else {
|
||||
probe_text =
|
||||
'Did you have any thoughts or observations about the experiment?';
|
||||
}
|
||||
|
||||
jsPsych.data.addProperties({
|
||||
condition: probe_condition,
|
||||
together_colour: together_colour,
|
||||
prolific_id: prolific_id,
|
||||
experiment_name: experiment_name,
|
||||
probe_text: probe_text,
|
||||
});
|
||||
|
||||
const timeline = [];
|
||||
|
||||
const pre_consent_info = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
stimulus: `<h1 class="text-2xl font-bold">Hello, world!</h1>`,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('pre_consent_info'),
|
||||
};
|
||||
|
||||
const timeline = [demo_trial];
|
||||
const enter_fullscreen = {
|
||||
type: jsPsychFullscreen,
|
||||
fullscreen_mode: true,
|
||||
};
|
||||
|
||||
const consent_form = {
|
||||
type: jsPsychHtmlButtonResponse,
|
||||
stimulus: stimulusMap.get('consent'),
|
||||
choices: ['Exit', 'Continue'],
|
||||
on_finish: function(data) {
|
||||
if (data.response === 0) {
|
||||
jsPsych.abortExperiment(stimulusMap.get('no_consent'));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const initial_lobby = {
|
||||
type: jsPsychLobby,
|
||||
user_names: uniqueUsernames,
|
||||
end_number: total_participants,
|
||||
start_text: 'Searching for a partner',
|
||||
end_text: 'Partner found, the experiment will begin shortly.',
|
||||
join_interval: 5000,
|
||||
show_avatars: false,
|
||||
};
|
||||
|
||||
const lobby_slow = {
|
||||
type: jsPsychLobby,
|
||||
user_names: uniqueUsernames,
|
||||
end_number: total_participants,
|
||||
start_text: 'Waiting for your partner',
|
||||
end_text: 'Your partner is ready, the experiment will continue shortly.',
|
||||
show_avatars: false,
|
||||
join_interval: 7000,
|
||||
};
|
||||
|
||||
const lobby_fast = {
|
||||
type: jsPsychLobby,
|
||||
user_names: uniqueUsernames,
|
||||
end_number: total_participants,
|
||||
start_text: 'Waiting for your partner',
|
||||
end_text: 'Your partner is ready, the experiment will continue shortly.',
|
||||
show_avatars: false,
|
||||
join_interval: 500,
|
||||
};
|
||||
|
||||
const multi_user_instructions = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('multi_user_instructions'),
|
||||
};
|
||||
|
||||
const instructions_1 = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('instructions_1'),
|
||||
};
|
||||
|
||||
const instructions_2 = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('instructions_2'),
|
||||
};
|
||||
|
||||
const instructions_3 = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('instructions_3'),
|
||||
};
|
||||
|
||||
const instructions_4 = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('instructions_4'),
|
||||
};
|
||||
|
||||
const pre_practice_instructions = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('pre_practice_instructions'),
|
||||
};
|
||||
|
||||
const pre_task_instructions = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('pre_task_instructions'),
|
||||
};
|
||||
|
||||
const debrief = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
stimulus: stimulusMap.get('debrief'),
|
||||
};
|
||||
|
||||
const survey = {
|
||||
type: jsPsychSurvey,
|
||||
survey_json: {
|
||||
showQuestionNumbers: false,
|
||||
completeText: 'Done!',
|
||||
pageNextText: 'Continue',
|
||||
pagePrevText: 'Previous',
|
||||
showPrevButton: false,
|
||||
pages: [
|
||||
{
|
||||
name: 'page1',
|
||||
elements: [
|
||||
{
|
||||
type: 'radiogroup',
|
||||
title: 'Please indicate your gender',
|
||||
choices: ['Male', 'Female', 'Other'],
|
||||
isRequired: debug ? false : true,
|
||||
colCount: 0,
|
||||
name: 'gender',
|
||||
},
|
||||
{
|
||||
type: 'radiogroup',
|
||||
title: 'Please indicate your handedness',
|
||||
choices: ['Right', 'Left', 'Ambidextrous/Other'],
|
||||
isRequired: debug ? false : true,
|
||||
colCount: 0,
|
||||
name: 'handedness',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: 'How old are you?',
|
||||
name: 'age',
|
||||
isRequired: debug ? false : false,
|
||||
inputType: 'number',
|
||||
min: 18,
|
||||
max: 100,
|
||||
defaultValue: 18,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'page2',
|
||||
elements: [
|
||||
{
|
||||
type: 'comment',
|
||||
title: probe_text,
|
||||
name: 'probe',
|
||||
isRequired: debug ? false : true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'page3',
|
||||
elements: [
|
||||
{
|
||||
type: 'matrix',
|
||||
name:
|
||||
'To what extent do you agree with the following statements about your partner?',
|
||||
alternateRows: true,
|
||||
isAllRowRequired: debug ? false : true,
|
||||
rows: [
|
||||
{
|
||||
text: 'I did not believe that my partner was a real person.',
|
||||
value: 'SuspicionPartner',
|
||||
},
|
||||
{
|
||||
text: 'I believed that my partner was a real person.',
|
||||
value: 'ConfidencePartner',
|
||||
},
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
value: 5,
|
||||
text: 'Strongly agree',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
text: 'Agree',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
text: 'Neutral',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
text: 'Disagree',
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
text: 'Strongly disagree',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const factors = {
|
||||
selector: ['partner', 'participant'],
|
||||
together_side: ['left', 'right'],
|
||||
location: short_version ? [33, 50, 66] : [32, 33, 34, 49, 50, 51, 65, 66, 67],
|
||||
};
|
||||
|
||||
const practice_factors = {
|
||||
selector: ['partner', 'participant'],
|
||||
together_side: ['left', 'right'],
|
||||
location: [50],
|
||||
};
|
||||
|
||||
const trials = jsPsych.randomization.factorial(factors, 1);
|
||||
|
||||
if (debug) {
|
||||
console.log(factors);
|
||||
console.log(trials);
|
||||
}
|
||||
|
||||
const practice_trials = jsPsych.randomization.factorial(practice_factors, 1);
|
||||
|
||||
const object_moving_trials = {
|
||||
timeline: [
|
||||
{
|
||||
type: jsPsychObjectMoving,
|
||||
selector: jsPsych.timelineVariable('selector'),
|
||||
together_side: jsPsych.timelineVariable('together_side'),
|
||||
location: jsPsych.timelineVariable('location'),
|
||||
together_colour: together_colour,
|
||||
},
|
||||
],
|
||||
timeline_variables: trials,
|
||||
};
|
||||
|
||||
const object_moving_practice = {
|
||||
timeline: [
|
||||
{
|
||||
type: jsPsychObjectMoving,
|
||||
selector: jsPsych.timelineVariable('selector'),
|
||||
together_side: jsPsych.timelineVariable('together_side'),
|
||||
location: jsPsych.timelineVariable('location'),
|
||||
together_colour: together_colour,
|
||||
},
|
||||
],
|
||||
timeline_variables: practice_trials,
|
||||
};
|
||||
|
||||
if (debug) {
|
||||
timeline.push(survey);
|
||||
timeline.push(debrief);
|
||||
}
|
||||
|
||||
if (!debug) {
|
||||
timeline.push(pre_consent_info);
|
||||
timeline.push(consent_form);
|
||||
timeline.push(enter_fullscreen);
|
||||
timeline.push(multi_user_instructions);
|
||||
timeline.push(initial_lobby);
|
||||
timeline.push(instructions_1);
|
||||
timeline.push(instructions_2);
|
||||
timeline.push(instructions_3);
|
||||
timeline.push(instructions_4);
|
||||
timeline.push(pre_practice_instructions);
|
||||
timeline.push(lobby_slow);
|
||||
timeline.push(object_moving_practice);
|
||||
timeline.push(pre_task_instructions);
|
||||
timeline.push(lobby_fast);
|
||||
timeline.push(object_moving_trials);
|
||||
timeline.push(survey);
|
||||
timeline.push(debrief);
|
||||
}
|
||||
jsPsych.run(timeline);
|
||||
|
||||
27
scripts/colours.js
Normal file
@@ -0,0 +1,27 @@
|
||||
export default function getColourMap(together_colour) {
|
||||
const colourMap = new Map();
|
||||
// Some reduncancy here, but Tailwind wont recognise the classes otherwise
|
||||
colourMap.set('go', 'text-lime-600');
|
||||
colourMap.set('warning', 'text-yellow-600');
|
||||
colourMap.set('self-username', 'text-sky-600');
|
||||
colourMap.set('success', 'text-green-600');
|
||||
colourMap.set('gobg', 'bg-lime-600');
|
||||
|
||||
colourMap.set('object-enabled', 'bg-gray-600');
|
||||
colourMap.set('object-disabled', 'bg-gray-400');
|
||||
|
||||
if (together_colour === 'blue') {
|
||||
colourMap.set('alone', 'text-rose-400');
|
||||
colourMap.set('together', 'text-blue-400');
|
||||
|
||||
colourMap.set('alonebg', 'bg-rose-400');
|
||||
colourMap.set('togetherbg', 'bg-blue-400');
|
||||
} else if (together_colour === 'red') {
|
||||
colourMap.set('alone', 'text-blue-400');
|
||||
colourMap.set('together', 'text-rose-400');
|
||||
|
||||
colourMap.set('alonebg', 'bg-blue-400');
|
||||
colourMap.set('togetherbg', 'bg-rose-400');
|
||||
}
|
||||
return colourMap;
|
||||
}
|
||||
86
scripts/deploy.js
Normal file
@@ -0,0 +1,86 @@
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const archiver = require('archiver');
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
// Load environment variables based on NODE_ENV
|
||||
const envFile =
|
||||
process.env.NODE_ENV === 'production'
|
||||
? '.env.production'
|
||||
: '.env.development';
|
||||
dotenv.config({ path: path.join(__dirname, '..', envFile) });
|
||||
|
||||
// Function to create and send zip file
|
||||
function sendZipFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const distPath = path.join(__dirname, '../dist');
|
||||
const zipPath = path.join(__dirname, '../dist.zip');
|
||||
|
||||
// Create a file to stream archive data to
|
||||
const output = fs.createWriteStream(zipPath);
|
||||
const archive = archiver('zip', {
|
||||
zlib: { level: 9 }, // Sets the compression level
|
||||
});
|
||||
|
||||
// Listen for all archive data to be written
|
||||
output.on('close', () => {
|
||||
console.log(`✅ Archive created: ${archive.pointer()} total bytes`);
|
||||
|
||||
// Get the deployment URL from environment
|
||||
const deployUrl = process.env.VITE_DEPLOY_URL;
|
||||
if (!deployUrl) {
|
||||
console.error('❌ VITE_DEPLOY_URL not found in environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Send the zip file via curl
|
||||
try {
|
||||
console.log(`📤 Sending zip file to ${deployUrl}...`);
|
||||
execSync(`curl -X POST -F "file=@${zipPath}" ${deployUrl}`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
console.log('✅ Zip file sent successfully');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to send zip file:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Clean up the zip file
|
||||
fs.unlinkSync(zipPath);
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Handle warnings and errors
|
||||
archive.on('warning', err => {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.warn('⚠️ Archive warning:', err);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
archive.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
// Pipe archive data to the file
|
||||
archive.pipe(output);
|
||||
|
||||
// Add the dist directory to the archive
|
||||
archive.directory(distPath, false);
|
||||
|
||||
// Finalize the archive
|
||||
archive.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
// Run the deployment
|
||||
sendZipFile()
|
||||
.then(() => {
|
||||
console.log('✅ Deployment successful!');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Deployment failed:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
77
scripts/name-gen.js
Normal file
@@ -0,0 +1,77 @@
|
||||
function generateRandomUsername(usedAdjectives, usedNouns) {
|
||||
const adjectives = [
|
||||
'Round',
|
||||
'Square',
|
||||
'Tall',
|
||||
'Wide',
|
||||
'Small',
|
||||
'Large',
|
||||
'Flat',
|
||||
'Smooth',
|
||||
'Long',
|
||||
'Short',
|
||||
'Straight',
|
||||
'Curved',
|
||||
'Solid',
|
||||
'Hollow',
|
||||
'Dense',
|
||||
'Light',
|
||||
'Deep',
|
||||
'Broad',
|
||||
'Thin',
|
||||
'Thick',
|
||||
];
|
||||
const nouns = [
|
||||
'Table',
|
||||
'Chair',
|
||||
'Lamp',
|
||||
'Spoon',
|
||||
'Book',
|
||||
'Door',
|
||||
'Window',
|
||||
'Pencil',
|
||||
'Paper',
|
||||
'Bowl',
|
||||
'Plate',
|
||||
'Cup',
|
||||
'Box',
|
||||
'Shelf',
|
||||
'Frame',
|
||||
'Desk',
|
||||
'Mirror',
|
||||
'Basket',
|
||||
'Button',
|
||||
'Bottle',
|
||||
];
|
||||
|
||||
let randomAdjective;
|
||||
do {
|
||||
randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
|
||||
} while (usedAdjectives.has(randomAdjective));
|
||||
|
||||
let randomNoun;
|
||||
do {
|
||||
randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
|
||||
} while (usedNouns.has(randomNoun));
|
||||
|
||||
usedAdjectives.add(randomAdjective);
|
||||
usedNouns.add(randomNoun);
|
||||
|
||||
const randomInt = Math.floor(Math.random() * 100);
|
||||
|
||||
return randomAdjective + randomNoun + randomInt;
|
||||
}
|
||||
|
||||
function generateUniqueUsernames(count) {
|
||||
const usernames = new Set();
|
||||
const usedAdjectives = new Set();
|
||||
const usedNouns = new Set();
|
||||
|
||||
while (usernames.size < count) {
|
||||
usernames.add(generateRandomUsername(usedAdjectives, usedNouns));
|
||||
}
|
||||
|
||||
return Array.from(usernames);
|
||||
}
|
||||
|
||||
export default generateUniqueUsernames;
|
||||
179
scripts/plugin-lobby.js
Normal file
@@ -0,0 +1,179 @@
|
||||
import { ParameterType } from 'jspsych';
|
||||
import { html } from './text-stimuli';
|
||||
import getColourMap from './colours';
|
||||
|
||||
const info = {
|
||||
name: 'lobby',
|
||||
version: "1.0",
|
||||
parameters: {
|
||||
user_names: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'User names',
|
||||
default: [],
|
||||
array: true,
|
||||
},
|
||||
start_text: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'Start text',
|
||||
},
|
||||
end_text: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'End text',
|
||||
},
|
||||
start_number: {
|
||||
type: ParameterType.INT,
|
||||
pretty_name: 'Start number',
|
||||
default: null,
|
||||
},
|
||||
end_number: {
|
||||
type: ParameterType.INT,
|
||||
pretty_name: 'End number',
|
||||
default: 3,
|
||||
},
|
||||
join_interval: {
|
||||
type: ParameterType.INT,
|
||||
pretty_name: 'Join interval',
|
||||
default: 5000,
|
||||
},
|
||||
user_object: {
|
||||
type: ParameterType.OBJECT,
|
||||
pretty_name: 'User object',
|
||||
default: {},
|
||||
},
|
||||
show_avatars: {
|
||||
type: ParameterType.BOOL,
|
||||
pretty_name: 'Show avatar',
|
||||
default: false,
|
||||
},
|
||||
data: {
|
||||
type: ParameterType.OBJECT,
|
||||
pretty_name: 'Data',
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
// prettier-ignore
|
||||
citations: '__CITATIONS__'
|
||||
};
|
||||
|
||||
class jsPsychLobby {
|
||||
constructor(jsPsych) {
|
||||
this.jsPsych = jsPsych;
|
||||
}
|
||||
static {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
trial(display_element, trial) {
|
||||
let current_participants;
|
||||
if (!trial.start_number) {
|
||||
current_participants = Math.floor(Math.random() * trial.end_number) + 1;
|
||||
} else {
|
||||
current_participants = trial.start_number;
|
||||
}
|
||||
const colourMap = getColourMap(' ');
|
||||
|
||||
display_element.innerHTML = html`<div id="search-text" class="mb-4"></div>
|
||||
<div id="participants-list"></div>
|
||||
<div id="loading-animation" class="loading"></div>
|
||||
<div id="refresh-warning" class="mt-4"></div>`;
|
||||
|
||||
const search_text_div = document.getElementById('search-text');
|
||||
const participants_list_div = document.getElementById('participants-list');
|
||||
const refresh_warning_div = document.getElementById('refresh-warning');
|
||||
const loading_animation_div = document.getElementById('loading-animation');
|
||||
|
||||
search_text_div.innerHTML =
|
||||
'<p class="text-lg font-semibold" id="search-text-p">' +
|
||||
trial.start_text +
|
||||
' (<span id="current-participants">' +
|
||||
current_participants +
|
||||
'</span>/' +
|
||||
trial.end_number +
|
||||
') ' +
|
||||
'</p>';
|
||||
|
||||
refresh_warning_div.innerHTML =
|
||||
`<p class="text-sm font-semibold ${colourMap.get('warning')}">Please do not refresh or close this page unless you are not connected to your partner within 5 minutes</p>`;
|
||||
|
||||
const current_participants_span = document.getElementById('current-participants');
|
||||
|
||||
let participants_list = trial.user_names.slice(1, current_participants);
|
||||
|
||||
let ended = false;
|
||||
|
||||
const avatar_svg_template = colour => html`
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="${colour}"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="0.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const createParticipantsHtml = () => {
|
||||
let html = '';
|
||||
for (let i = 0; i < participants_list.length; i++) {
|
||||
/* if (trial.show_avatars) {
|
||||
html += `<div style="width: 24px; height: 24px;">${avatar_svg_template(
|
||||
trial.user_object[participants_list[i]]
|
||||
)}</div>`;
|
||||
} */
|
||||
if (participants_list[i] === trial.user_names[0]) {
|
||||
html += `<p class="text-base ${colourMap.get('self-username')}">` + participants_list[i] + ` (you)</p>`;
|
||||
} else {
|
||||
html += `<p class="text-base">` + participants_list[i] + ` </p>`;
|
||||
}
|
||||
}
|
||||
return (html);
|
||||
};
|
||||
|
||||
|
||||
participants_list_div.innerHTML = createParticipantsHtml();
|
||||
|
||||
setTimeout(() => {
|
||||
participants_list.push(trial.user_names[0]);
|
||||
participants_list_div.innerHTML = createParticipantsHtml();
|
||||
}, 100);
|
||||
|
||||
const updateParticipants = () => {
|
||||
if (current_participants >= trial.end_number && !ended) {
|
||||
ended = true;
|
||||
clearInterval(participants_interval);
|
||||
setTimeout(this.foundParticipants, 1500);
|
||||
} else {
|
||||
participants_list.push(trial.user_names[current_participants]);
|
||||
participants_list_div.innerHTML = createParticipantsHtml();
|
||||
current_participants++;
|
||||
if (current_participants <= trial.end_number && !ended) {
|
||||
current_participants_span.innerHTML = current_participants;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.foundParticipants = () => {
|
||||
participants_list_div.innerHTML = html`<span class="${colourMap.get('success')} font-semibold">${trial.end_text}</span>`;
|
||||
loading_animation_div.classList.remove('loading');
|
||||
setTimeout(() => {
|
||||
this.jsPsych.finishTrial();
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const participants_interval = setInterval(
|
||||
updateParticipants,
|
||||
trial.join_interval
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default jsPsychLobby;
|
||||
|
||||
|
||||
|
||||
302
scripts/plugin-object-moving.js
Normal file
@@ -0,0 +1,302 @@
|
||||
import { ParameterType } from 'jspsych';
|
||||
import { html } from './text-stimuli';
|
||||
import getColourMap from './colours';
|
||||
|
||||
const info = {
|
||||
name: 'object-moving',
|
||||
version: "1.1",
|
||||
parameters: {
|
||||
selector: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'Selector',
|
||||
},
|
||||
together_side: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'together side',
|
||||
},
|
||||
location: {
|
||||
type: ParameterType.INT,
|
||||
pretty_name: 'Location',
|
||||
},
|
||||
together_colour: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'Together colour',
|
||||
},
|
||||
},
|
||||
// prettier-ignore
|
||||
citations: '__CITATIONS__'
|
||||
};
|
||||
|
||||
class jsPsychObjectMoving {
|
||||
constructor(jsPsych) {
|
||||
this.jsPsych = jsPsych;
|
||||
}
|
||||
static {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
trial(display_element, trial) {
|
||||
|
||||
const colourMap = getColourMap(trial.together_colour);
|
||||
|
||||
const left_goal = trial.together_side === 'left' ? 'together' : 'alone';
|
||||
const right_goal = trial.together_side === 'left' ? 'alone' : 'together';
|
||||
|
||||
const left_colour = trial.together_side === 'left' ? colourMap.get('togetherbg') : colourMap.get('alonebg');
|
||||
const right_colour = trial.together_side === 'left' ? colourMap.get('alonebg') : colourMap.get('togetherbg');
|
||||
|
||||
display_element.innerHTML = html`
|
||||
<div class="flex flex-row min-w-screen absolute top-0 left-0 min-h-screen">
|
||||
<div id="${left_goal}-goal" class="w-1/12 ${left_colour}"></div>
|
||||
<div id="workspace" class="w-10/12"></div>
|
||||
<div id="${right_goal}-goal" class="w-1/12 ${right_colour}"></div>
|
||||
<div id="info-box" class="z-10 absolute bottom-1/40 min-w-screen justify-center items-center"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const workspace = document.getElementById('workspace');
|
||||
const info_box = document.getElementById('info-box');
|
||||
const workspace_width = workspace.clientWidth;
|
||||
const viewport_width = window.visualViewport.width;
|
||||
const goal_offset = viewport_width * (1/12);
|
||||
const box_width = viewport_width * (3/100);
|
||||
const workspace_offset = workspace_width * trial.location/100;
|
||||
|
||||
const offset_left = workspace_offset + goal_offset - box_width/2;
|
||||
|
||||
const viewport_height = window.visualViewport.height;
|
||||
const top_offset = viewport_height * .5;
|
||||
const separation = box_width * 1.5;
|
||||
|
||||
const objects = html`
|
||||
<div id="object-partner" class="object-moving-box ${colourMap.get('object-disabled')} absolute z-10" style="left: ${offset_left}px; top: ${top_offset - separation}px"></div>
|
||||
<div id="object-participant" class="object-moving-box ${colourMap.get('object-disabled')} absolute z-10" style="left: ${offset_left}px; top: ${top_offset + separation}px"></div>
|
||||
`;
|
||||
|
||||
workspace.innerHTML = objects;
|
||||
|
||||
const object_partner = document.getElementById('object-partner');
|
||||
const object_participant = document.getElementById('object-participant');
|
||||
|
||||
object_partner.addEventListener('click', () => issue_alert('partner_click'));
|
||||
object_participant.addEventListener('click', () => issue_alert('early_click_choice'));
|
||||
|
||||
const partner_turn = html`<span class="font-semi-bold text-gray-800 text-xl">Awaiting your partner's selection</span>
|
||||
<div class='loading'></div>`
|
||||
|
||||
let choice;
|
||||
let go_clicked = false;
|
||||
let partner_finished = false;
|
||||
let participant_finished = false;
|
||||
let partner_choice_made = false;
|
||||
|
||||
const jsPsych = this.jsPsych;
|
||||
|
||||
let start_time = 0;
|
||||
let response_time = 0;
|
||||
|
||||
if (trial.selector === 'participant') {
|
||||
display_choices();
|
||||
|
||||
} else {
|
||||
info_box.innerHTML = partner_turn;
|
||||
|
||||
const partner_choice_delay = jsPsych.randomization.sampleExGaussian(2000, 250, 1/100, true);
|
||||
|
||||
let random = Math.random();
|
||||
|
||||
|
||||
//These probabilities are based on Azaad and Sebanz (2025)
|
||||
|
||||
if (trial.location > 48 && trial.location < 52) {
|
||||
choice = random < 0.64 ? 'together' : 'alone';
|
||||
} else if (trial.location > 31 && trial.location < 35) {
|
||||
choice = random < 0.58 ? 'together' : 'alone';
|
||||
} else if (trial.location > 64 && trial.location < 68) {
|
||||
choice = random < 0.67 ? 'together' : 'alone';
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
info_box.innerHTML = '';
|
||||
clear_boxes();
|
||||
display_go();
|
||||
initiate_partner_action();
|
||||
}, partner_choice_delay);
|
||||
}
|
||||
|
||||
function display_choices(){
|
||||
info_box.innerHTML = html`<div class="flex flex-row justify-center items-center gap-2">
|
||||
<div id="${left_goal}-button" class="w-1/12 py-1 text-slate-50 ${left_colour}">${left_goal.charAt(0).toUpperCase() + left_goal.slice(1)}</div>
|
||||
<div id="${right_goal}-button" class="w-1/12 py-1 text-slate-50 ${right_colour}">${right_goal.charAt(0).toUpperCase() + right_goal.slice(1)}</div>
|
||||
</div>`;
|
||||
|
||||
const left_goal_button = document.getElementById(`${left_goal}-button`);
|
||||
const right_goal_button = document.getElementById(`${right_goal}-button`);
|
||||
|
||||
start_time = Date.now();
|
||||
|
||||
left_goal_button.addEventListener('click', () => {
|
||||
choice = left_goal;
|
||||
handle_choice_made(left_goal_button,right_goal_button);
|
||||
});
|
||||
|
||||
right_goal_button.addEventListener('click', () => {
|
||||
choice = right_goal;
|
||||
handle_choice_made(left_goal_button,right_goal_button);
|
||||
});
|
||||
}
|
||||
|
||||
function handle_choice_made(left_goal_button,right_goal_button){
|
||||
left_goal_button.removeEventListener('click', handle_choice_made);
|
||||
right_goal_button.removeEventListener('click', handle_choice_made);
|
||||
left_goal_button.remove();
|
||||
right_goal_button.remove();
|
||||
clear_boxes();
|
||||
display_go();
|
||||
response_time = Date.now() - start_time;
|
||||
if (choice === 'together') {
|
||||
initiate_partner_action();
|
||||
}
|
||||
}
|
||||
|
||||
function initiate_partner_action(){
|
||||
const go_delay = trial.selector === 'participant' ? 1200 : 800
|
||||
const partner_go_delay = jsPsych.randomization.sampleExGaussian(go_delay, 300, 1/100, true);
|
||||
const partner_teleport_delay = jsPsych.randomization.sampleExGaussian(1200, 200, 1/100, true);
|
||||
|
||||
setTimeout(() => {
|
||||
change_object_colour(object_partner);
|
||||
partner_choice_made = true;
|
||||
}, partner_go_delay);
|
||||
|
||||
setTimeout(() => {
|
||||
teleport_object(object_partner);
|
||||
partner_finished = true;
|
||||
check_finished();
|
||||
}, partner_teleport_delay + partner_go_delay);
|
||||
}
|
||||
|
||||
function clear_boxes() {
|
||||
if (choice === 'together') {
|
||||
return;
|
||||
}
|
||||
if (trial.selector === 'participant') {
|
||||
object_partner.remove();
|
||||
} else {
|
||||
object_participant.remove();
|
||||
}
|
||||
}
|
||||
|
||||
let go_button;
|
||||
|
||||
function display_go() {
|
||||
if (choice === 'alone' && trial.selector === 'partner') {
|
||||
return;
|
||||
}
|
||||
info_box.innerHTML = html`<div id="go-button" class="mx-auto font-semi-bold ${colourMap.get('gobg')} text-slate-50 text-xl w-1/20 p-2">Go</div>`;
|
||||
go_button = document.getElementById('go-button');
|
||||
object_participant.addEventListener('click', () => issue_alert('early_click_go'));
|
||||
go_button.addEventListener('click', () => handle_go_click(go_button));
|
||||
}
|
||||
|
||||
function handle_go_click() {
|
||||
go_button.removeEventListener('click', handle_go_click);
|
||||
go_button.removeEventListener('click', () => issue_alert('early_click_go'));
|
||||
go_clicked = true;
|
||||
go_button.remove();
|
||||
change_object_colour(object_participant);
|
||||
object_participant.addEventListener('click', () => handle_teleport_click());
|
||||
|
||||
}
|
||||
|
||||
function handle_teleport_click(){
|
||||
teleport_object(object_participant);
|
||||
participant_finished = true;
|
||||
check_finished();
|
||||
}
|
||||
|
||||
|
||||
function change_object_colour(object){
|
||||
object.classList.remove(colourMap.get('object-disabled'));
|
||||
object.classList.add(colourMap.get('object-enabled'));
|
||||
}
|
||||
|
||||
function teleport_object(object){
|
||||
let goal_side = 'left';
|
||||
if (choice === 'together' && trial.together_side === 'right') {
|
||||
goal_side = 'right';
|
||||
}
|
||||
if (choice === 'alone' && trial.together_side === 'left') {
|
||||
goal_side = 'right';
|
||||
}
|
||||
const mid_goal_offset = viewport_width * (1/24);
|
||||
const new_left_offset = goal_side === 'left' ? mid_goal_offset - box_width/2 : viewport_width - mid_goal_offset - box_width/2;
|
||||
object.style.transition = 'left 0.35s';
|
||||
object.style.left = `${new_left_offset}px`;
|
||||
}
|
||||
|
||||
function check_finished(){
|
||||
let finished = false;
|
||||
if (choice === 'together') {
|
||||
if (partner_finished && participant_finished) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
if (choice === 'alone') {
|
||||
if (partner_finished || participant_finished) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
if (finished) {
|
||||
object_partner.removeEventListener('click', () => issue_alert('partner_click'));
|
||||
object_participant.removeEventListener('click', () => issue_alert('early_click_choice'));
|
||||
|
||||
const save_data = {
|
||||
choice: choice,
|
||||
selector: trial.selector,
|
||||
together_side: trial.together_side,
|
||||
response_time: response_time,
|
||||
}
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
jsPsych.finishTrial(save_data);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
async function show_alert(message) {
|
||||
const dialog = document.createElement("dialog");
|
||||
document.body.appendChild(dialog);
|
||||
dialog.className = "z-20 fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 p-4 bg-slate-100 text-red-700 rounded-lg shadow-lg";
|
||||
dialog.innerText = message;
|
||||
dialog.show();
|
||||
setTimeout(function () {
|
||||
dialog.close();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function issue_alert(issue){
|
||||
if (issue === 'partner_click'){
|
||||
show_alert(`That is your partner's object. Please click the lower object.`)
|
||||
}
|
||||
if (issue === 'early_click_go' && !go_clicked){
|
||||
show_alert(`You need to click 'Go' before clicking your object.`)
|
||||
}
|
||||
if (issue === 'early_click_choice'){
|
||||
if (!partner_choice_made && trial.selector === 'partner'){
|
||||
show_alert(`Please wait for your partner to make a choice.`)
|
||||
}
|
||||
if (!choice && trial.selector === 'participant'){
|
||||
show_alert(`Please make a choice using the buttons below before clicking your object.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default jsPsychObjectMoving;
|
||||
|
||||
|
||||
|
||||
370
scripts/text-stimuli.js
Normal file
@@ -0,0 +1,370 @@
|
||||
import getColourMap from './colours.js';
|
||||
|
||||
// Define image URLs
|
||||
const blueImages = {
|
||||
instructions_1: '/images/blue/instructions_1.png',
|
||||
instructions_2: '/images/blue/instructions_2.png',
|
||||
instructions_3: '/images/blue/instructions_3.png',
|
||||
instructions_4: '/images/blue/instructions_4.png',
|
||||
};
|
||||
|
||||
const redImages = {
|
||||
instructions_1: '/images/red/instructions_1.png',
|
||||
instructions_2: '/images/red/instructions_2.png',
|
||||
instructions_3: '/images/red/instructions_3.png',
|
||||
instructions_4: '/images/red/instructions_4.png',
|
||||
};
|
||||
|
||||
const html = (strings, ...values) =>
|
||||
strings
|
||||
.reduce((result, str, i) => result + str + (values[i] || ''), '')
|
||||
.trim();
|
||||
|
||||
function getStimulusMap(together_colour) {
|
||||
const colourMap = getColourMap(together_colour);
|
||||
|
||||
const stimulusMap = new Map();
|
||||
|
||||
// Select the appropriate image map based on the color
|
||||
const imageMap = together_colour === 'blue' ? blueImages : redImages;
|
||||
|
||||
stimulusMap.set(
|
||||
'pre_consent_info',
|
||||
html`
|
||||
<p class="leading-relaxed">
|
||||
This experiment will need some browser permissions. Please ensure that,
|
||||
when prompted, you allow the experiment to run in fullscreen and that
|
||||
you allow yourself to be redirected to Prolific after the experiment is
|
||||
complete.
|
||||
</p>
|
||||
<p class="mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'consent',
|
||||
html`
|
||||
<div class="text-left leading-relaxed">
|
||||
<h1 class="text-2xl font-bold mt-4">
|
||||
General Participant Information and Consent
|
||||
</h1>
|
||||
<h2 class="text-xl font-bold mt-2">
|
||||
Title of the study: Decision-making with Others
|
||||
</h2>
|
||||
<p class="font-semibold mt-2">1. Description of the research project</p>
|
||||
Welcome to our study on Decision-making with Others. We are
|
||||
investigating how people choose courses of action when working with
|
||||
others. The experiment will take less than 20 minutes, with short pauses
|
||||
in between. However, since this is a multi-participant study, we ask
|
||||
that you complete the task fully without taking additional breaks. If
|
||||
you have any further questions, please contact the investigator.
|
||||
<p class="font-semibold mt-2">
|
||||
2. Voluntary participation and anonymity
|
||||
</p>
|
||||
Participation in the study is voluntary. You can withdraw your consent
|
||||
to participate in this study at any time and without giving reasons and
|
||||
without suffering any disadvantages. Even if you terminate the study
|
||||
early, you are entitled to compensation for your time up to that point.
|
||||
<p class="font-semibold mt-2">3. Compensation</p>
|
||||
For participating in the study, you will receive the rate indicated on
|
||||
the Prolific.com page for this study. The fee will be paid through the
|
||||
Prolific platform.
|
||||
<p class="font-semibold mt-2">
|
||||
4. Scope of data collection and processing
|
||||
</p>
|
||||
We will save your responses and judgements to the questions and stimuli
|
||||
presented in this study. We will also collect information about your
|
||||
response times. We also ask for your gender, age, and thoughts about the
|
||||
study following completion. These data will be de-identified, so that
|
||||
your responses are not saved in a way that one could identify you from
|
||||
your responses (or which responses are yours). The results and data of
|
||||
this study will be published as a scientific publication. This will be
|
||||
done in an anonymized form, i.e. without the data being assigned to a
|
||||
specific person. The completely anonymized data of this study will be
|
||||
made available as open data in a secure, internet-based data archive
|
||||
(osf.io). This study thus follows the recommendations of the German
|
||||
Research Foundation (DFG) and the German Society for Psychology (DGPs)
|
||||
for quality assurance in research.
|
||||
<p class="font-semibold mt-2">5. Legal basis</p>
|
||||
The legal basis for processing the personal data mentioned is the
|
||||
consent in accordance with Art. 6 (1) letter a EU GDPR.
|
||||
<p class="font-semibold mt-2">6. Revocation</p>
|
||||
You have the right to revoke your consent to data protection at any
|
||||
time. The revocation of your consent does not affect the legality of the
|
||||
processing carried out on the basis of your consent until the
|
||||
revocation. (Revocation with effect for the future). Address your
|
||||
revocation to the person responsible. You will not suffer any
|
||||
disadvantages as a result of the revocation
|
||||
<p class="font-semibold mt-2">
|
||||
7. Name and Address of the person responsible
|
||||
</p>
|
||||
The controller within the meaning of the EU General Data Protection
|
||||
Regulation (GDPR) and other national data protection laws of the member
|
||||
states, as well as other data protection regulations is the Westfälische
|
||||
Wilhelms-Universität Münster (WWU), represented by the Rector, Prof. Dr.
|
||||
Johannes Wessels, Schlossplatz 2, 48149 MünsterTel.: + 49 251
|
||||
83-0E-Mail: verwaltung@uni-muenster.de
|
||||
<p class="font-semibold mt-2">
|
||||
8. Contact details of the data protection officer
|
||||
</p>
|
||||
The data protection officer of the WWU Münster is: Nina Meyer-Pachur
|
||||
Schlossplatz 2, 48149 Münster Tel.: + 49 251 83-22446 E-Mail:
|
||||
datenschutz@uni-muenster.de
|
||||
<p class="font-semibold mt-2">
|
||||
9. Reference to the rights of those affected
|
||||
</p>
|
||||
<p class="leading-relaxed">
|
||||
According to the General Data Protection Regulation, you basically
|
||||
have the right to: information (Article 15 GDPR), objection (Article
|
||||
21 GDPR), data portability (Article 20 GDPR), erasure (Article 17
|
||||
GDPR), restriction of processing (Article 18 GDPR), rectification
|
||||
(Article 16 GDPR). If you would like to exercise one of these rights,
|
||||
please contact one of the contact persons mentioned. You also have the
|
||||
right to lodge a complaint with the supervisory authority: State
|
||||
Commissioner for Data Protection and Freedom of Information North
|
||||
Rhine-Westphalia Helga Block Kavalleriestraße 2-440213
|
||||
DüsseldorfTelephone: 02 11/384 24-0E-Mail: poststelle@ldi.nrw.de
|
||||
Homepage: http://www.ldi.nrw.de
|
||||
</p>
|
||||
|
||||
<p class="font-semibold mt-2">
|
||||
10. Consent to the collection and processing of personal data
|
||||
</p>
|
||||
By clicking continue, I hereby voluntarily consent to the collection and
|
||||
processing of my personal data as part of the research project
|
||||
‘Decision-making with others’. I have read the data protection
|
||||
declaration for the project in question, have been adequately informed
|
||||
and have had the opportunity to ask questions. I have been informed of
|
||||
the consequences of revoking my consent under data protection law at any
|
||||
time. I have been informed that my revocation of my consent does not
|
||||
affect the legality of the processing carried out on the basis of the
|
||||
consent up to the time of revocation.
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'no_consent',
|
||||
html`
|
||||
<p class="leading-relaxed mt-6">
|
||||
You are being redirected to Prolific. If you are not redirected within 10 seconds, please paste the following link into your browser:
|
||||
<span class="text-blue-500">
|
||||
${import.meta.env.VITE_NO_CONSENT_URL}
|
||||
</a>
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'multi_user_instructions',
|
||||
html`
|
||||
<p class="leading-relaxed">
|
||||
In this experiment, you will connect and work with a partner.
|
||||
</p>
|
||||
<p class="leading-relaxed">
|
||||
It is
|
||||
<i>critical</i>
|
||||
that you only continue if you are able to complete the entire experiment
|
||||
without interruption and with a stable internet connection.
|
||||
</p>
|
||||
<p class="leading-relaxed mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
once you are ready to be paired with a partner.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'instructions_1',
|
||||
html`
|
||||
<p class="leading-relaxed">
|
||||
In this study, you will work with your partner to complete a task
|
||||
involving moving objects to one of two goals.
|
||||
</p>
|
||||
<p class="leading-relaxed mt-2">
|
||||
In each round, one of you will be assigned the role of the
|
||||
<strong>Selector.</strong>
|
||||
The Selector will choose to which goal the object(s) will be moved.
|
||||
</p>
|
||||
<img
|
||||
class="w-6/12 mx-auto my-8 border-3 border-gray-500 rounded-md"
|
||||
src="${imageMap.instructions_1}"
|
||||
alt="instructions_1"
|
||||
/>
|
||||
<p class="leading-relaxed mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'instructions_2',
|
||||
// prettier-ignore
|
||||
html`
|
||||
<p class="leading-relaxed">
|
||||
One of the goals (labelled
|
||||
<span class="font-semibold ${colourMap.get('together')}">
|
||||
Together</span>)
|
||||
will require both of you to move an object each. The other goal
|
||||
(labelled
|
||||
<span class="font-semibold ${colourMap.get('alone')}">
|
||||
Alone</span>)
|
||||
will
|
||||
<i>only</i>
|
||||
require the Selector to move an object.
|
||||
</p>
|
||||
<p class="leading-relaxed mt-2">
|
||||
When you are the Selector, you will see two buttons at the bottom of your
|
||||
screen. The buttons colours correspond to the colour of the goal location
|
||||
(e.g., red button for the red goal).
|
||||
</p>
|
||||
<img
|
||||
class="w-6/12 mx-auto my-8 border-3 border-gray-500 rounded-md"
|
||||
src="${imageMap.instructions_2}"
|
||||
alt="instructions_2"
|
||||
/>
|
||||
<p class="leading-relaxed mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'instructions_3',
|
||||
html`
|
||||
<p class="leading-relaxed">
|
||||
If you are not the Selector, you will need to wait until your partner
|
||||
makes a decision.
|
||||
</p>
|
||||
<p class="leading-relaxed mt-2">
|
||||
Once a decision is made, you will first need to click the
|
||||
<span class="${colourMap.get('go')} font-semibold">Go</span>
|
||||
button to make your object movable. Then, once you click an object, it
|
||||
will teleport to the goal location.
|
||||
</p>
|
||||
<img
|
||||
class="w-6/12 mx-auto my-8 border-3 border-gray-500 rounded-md"
|
||||
src="${imageMap.instructions_3}"
|
||||
alt="instructions_3"
|
||||
/>
|
||||
<p class="leading-relaxed mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'instructions_4',
|
||||
html`
|
||||
<p class="leading-relaxed">
|
||||
If the Selector chooses the
|
||||
<span class="font-semibold ${colourMap.get('alone')}">Alone</span>
|
||||
goal, one object will disappear, and the Selector will move the
|
||||
remaining object.
|
||||
</p>
|
||||
<p class="leading-relaxed mt-2">
|
||||
Otherwise, if the Selector chooses
|
||||
<span class="font-semibold ${colourMap.get('together')}">
|
||||
Together
|
||||
</span>
|
||||
, each of you will need to click an object - only click the object that
|
||||
is lower on your screen. The other is for your partner.
|
||||
</p>
|
||||
<img
|
||||
class="w-6/12 mx-auto my-8 border-3 border-gray-500 rounded-md"
|
||||
src="${imageMap.instructions_4}"
|
||||
alt="instructions_4"
|
||||
/>
|
||||
<p class="leading-relaxed mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'pre_practice_instructions',
|
||||
html`
|
||||
<p class="leading-relaxed">
|
||||
You will now complete a short practice task with your partner.
|
||||
</p>
|
||||
<p class="leading-relaxed mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'pre_task_instructions',
|
||||
html`
|
||||
<p class="leading-relaxed">
|
||||
Practice complete. You will now complete the main task. This will take
|
||||
around five minutes.
|
||||
</p>
|
||||
<p class="leading-relaxed">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'debrief',
|
||||
html`
|
||||
<div class="text-left leading-relaxed">
|
||||
<h1 class="text-2xl font-bold">Debriefing statement</h1>
|
||||
<h2 class="text-xl font-bold mt-2">
|
||||
Title of the study: Decision-making with others
|
||||
</h2>
|
||||
<p class="mt-2">
|
||||
Thank you for participating in our study. In this study, we seek to
|
||||
investigate 1) to what extent online participants suspect that their
|
||||
interaction partners are not real people and 2) how and when they
|
||||
report such suspicions.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
To this end, we created the appearance of additional participants in
|
||||
the experiment – in reality, you performed the task alone. The results
|
||||
of our study will lend insight into the mechanisms by which we form a
|
||||
shared understanding of the world around us.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
Should you have any additional questions, you may contact Dr Shaheed
|
||||
Azaad at sazaad@uni-muenster.de.
|
||||
</p>
|
||||
<p class="mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to return to Prolific.
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'complete',
|
||||
html`You are being redirected to Prolific. If you are not redirected within 10 seconds, please paste the following link into your browser:
|
||||
<span class="text-blue-500">
|
||||
${import.meta.env.VITE_COMPLETE_URL}
|
||||
</a>
|
||||
`
|
||||
);
|
||||
return stimulusMap;
|
||||
}
|
||||
|
||||
export { getStimulusMap, html };
|
||||
@@ -1,9 +0,0 @@
|
||||
import html from '../utils/html.js';
|
||||
|
||||
export const textStimuli = {
|
||||
complete: html`Experiment complete. Please paste the following link into your browser to confirm completion on Prolific:
|
||||
<span class="text-blue-500">
|
||||
${import.meta.env.VITE_COMPLETE_URL}
|
||||
</a>
|
||||
`,
|
||||
};
|
||||
31
styles.css
@@ -1 +1,32 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
.loading:after {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
-webkit-animation: ellipsis steps(4, end) 1500ms infinite;
|
||||
animation: ellipsis steps(4, end) 1500ms infinite;
|
||||
content: '\2026';
|
||||
/* ascii code for the ellipsis character */
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
@keyframes ellipsis {
|
||||
to {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes ellipsis {
|
||||
to {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
.jspsych-content {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.object-moving-box {
|
||||
width: 3vw;
|
||||
height: 3vw;
|
||||
}
|
||||