working prototype
This commit is contained in:
92
index.js
92
index.js
@@ -8,13 +8,17 @@ import '@jspsych/plugin-survey/css/survey.css';
|
||||
import './styles.css';
|
||||
import { getStimulusMap } from './scripts/text-stimuli.js';
|
||||
import { textStimuli } from './scripts/text-stimuli.js';
|
||||
import JsPsychDieRoll from './plugins/jspsych-die-roll.js';
|
||||
import JsPsychCaptcha from './plugins/jspsych-captcha.js';
|
||||
import html from './utils/html.js';
|
||||
|
||||
const experiment_name = import.meta.env.VITE_EXPERIMENT_NAME;
|
||||
|
||||
let prolific_id;
|
||||
let probe_condition; // will be set to ai or human based on the condition.
|
||||
let debug = false;
|
||||
let debug;
|
||||
let probe_order; // will be set to ai_first or human_first based on the condition
|
||||
const CAPTCHA_TRIAL_COUNT = debug ? 2 : 12; // set to 10 for main experiment
|
||||
|
||||
function delayed_redirect(url) {
|
||||
setTimeout(() => {
|
||||
@@ -34,9 +38,14 @@ const jsPsych = initJsPsych({
|
||||
console.log(jsPsych.data.get().json());
|
||||
}
|
||||
},
|
||||
show_progress_bar: true,
|
||||
});
|
||||
|
||||
debug = jsPsych.data.getURLVariable('debug') === 'true';
|
||||
debug =
|
||||
jsPsych.data.getURLVariable('debug') === 'true' ||
|
||||
import.meta.env.VITE_DEBUG === 'true';
|
||||
|
||||
console.log('debug:', debug);
|
||||
|
||||
prolific_id = jsPsych.data.getURLVariable('PROLIFIC_PID');
|
||||
|
||||
@@ -54,22 +63,28 @@ const probe_text_difficulty =
|
||||
'while the die roll was random, the difficulty of the captcha task was pre-determined and unrelated to the die roll. ' +
|
||||
probe_closing_text;
|
||||
|
||||
let die_result = 3;
|
||||
|
||||
switch (COND) {
|
||||
case 0:
|
||||
probe_condition = 'die';
|
||||
probe_order = 'die_first';
|
||||
die_result = 4;
|
||||
break;
|
||||
case 1:
|
||||
probe_condition = 'die';
|
||||
probe_order = 'die_first';
|
||||
die_result = 4;
|
||||
break;
|
||||
case 2:
|
||||
probe_condition = 'difficulty';
|
||||
probe_order = 'die_first';
|
||||
die_result = 4;
|
||||
break;
|
||||
case 3:
|
||||
probe_condition = 'difficulty';
|
||||
probe_order = 'die_first';
|
||||
die_result = 4;
|
||||
break;
|
||||
case 4:
|
||||
probe_condition = 'die';
|
||||
@@ -135,6 +150,18 @@ const instructions_1 = {
|
||||
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 debrief = {
|
||||
type: jsPsychHtmlKeyboardResponse,
|
||||
choices: [' '],
|
||||
@@ -147,6 +174,40 @@ const pre_survey_info = {
|
||||
stimulus: stimulusMap.get('pre_survey_info'),
|
||||
};
|
||||
|
||||
const die_roll_trial = {
|
||||
type: JsPsychDieRoll,
|
||||
prompt: html`
|
||||
<p class="mt-2">
|
||||
Click the die once to start it rolling, then click it again to stop.
|
||||
</p>
|
||||
`,
|
||||
rigged_value: die_result,
|
||||
roll_duration: 2200,
|
||||
result_template: `You rolled a {{value}} (moderate difficulty). Click 'continue' to proceed.`,
|
||||
};
|
||||
|
||||
const create_captcha_trial = trial_index => {
|
||||
return {
|
||||
type: JsPsychCaptcha,
|
||||
prompt:
|
||||
'<p class="leading-relaxed text-center">Enter the characters you see into the textbox below.</p>',
|
||||
difficulty: die_result,
|
||||
button_label: 'Continue',
|
||||
error_text: '',
|
||||
allow_refresh: false,
|
||||
require_correct: false,
|
||||
data: {
|
||||
trial_id: 'captcha_entry',
|
||||
captcha_index: trial_index + 1,
|
||||
captcha_total: CAPTCHA_TRIAL_COUNT,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const captcha_trials = Array.from({ length: CAPTCHA_TRIAL_COUNT }, (_, index) =>
|
||||
create_captcha_trial(index)
|
||||
);
|
||||
|
||||
const die_probe_row = {
|
||||
text:
|
||||
'I suspected that the die roll was not random, or the number I received was pre-determined.',
|
||||
@@ -209,6 +270,14 @@ const survey = {
|
||||
text: `I found the captcha task difficult.`,
|
||||
value: 'Difficulty',
|
||||
},
|
||||
{
|
||||
text: `I felt lucky during the die roll.`,
|
||||
value: 'Luck',
|
||||
},
|
||||
{
|
||||
text: `I felt unlucky during the die roll.`,
|
||||
value: 'Bad_luck',
|
||||
},
|
||||
{
|
||||
text: `I think I solved all the captchas correctly.`,
|
||||
value: 'Accuracy',
|
||||
@@ -331,18 +400,25 @@ const survey = {
|
||||
},
|
||||
};
|
||||
|
||||
const main_experiment_timeline = [
|
||||
instructions_1,
|
||||
instructions_2,
|
||||
die_roll_trial,
|
||||
instructions_3,
|
||||
...captcha_trials,
|
||||
pre_survey_info,
|
||||
survey,
|
||||
debrief,
|
||||
];
|
||||
|
||||
if (debug) {
|
||||
timeline.push(survey);
|
||||
timeline.push(debrief);
|
||||
timeline.push(...main_experiment_timeline);
|
||||
}
|
||||
|
||||
if (!debug) {
|
||||
timeline.push(pre_consent_info);
|
||||
timeline.push(consent_form);
|
||||
timeline.push(enter_fullscreen);
|
||||
timeline.push(instructions_1);
|
||||
timeline.push(pre_survey_info);
|
||||
timeline.push(survey);
|
||||
timeline.push(debrief);
|
||||
timeline.push(...main_experiment_timeline);
|
||||
}
|
||||
jsPsych.run(timeline);
|
||||
|
||||
303
plugins/jspsych-captcha.js
Normal file
303
plugins/jspsych-captcha.js
Normal file
@@ -0,0 +1,303 @@
|
||||
import { ParameterType } from 'jspsych';
|
||||
|
||||
const info = {
|
||||
name: 'captcha',
|
||||
parameters: {
|
||||
prompt: {
|
||||
type: ParameterType.HTML_STRING,
|
||||
default: '',
|
||||
},
|
||||
difficulty: {
|
||||
type: ParameterType.INT,
|
||||
default: 3,
|
||||
},
|
||||
button_label: {
|
||||
type: ParameterType.STRING,
|
||||
default: 'Submit',
|
||||
},
|
||||
allow_refresh: {
|
||||
type: ParameterType.BOOL,
|
||||
default: true,
|
||||
},
|
||||
require_correct: {
|
||||
type: ParameterType.BOOL,
|
||||
default: true,
|
||||
},
|
||||
error_text: {
|
||||
type: ParameterType.HTML_STRING,
|
||||
default: 'That entry did not match, please try again.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
class CaptchaPlugin {
|
||||
constructor(jsPsych) {
|
||||
this.jsPsych = jsPsych;
|
||||
this.characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
}
|
||||
|
||||
trial(display_element, trial) {
|
||||
const start_time = performance.now();
|
||||
const attempt_counts = { submission_count: 0, refresh_count: 0 };
|
||||
const difficulty_settings = this.resolve_difficulty(trial.difficulty);
|
||||
let captcha_text = this.generate_text(difficulty_settings.length);
|
||||
let last_target_at_submission = captcha_text;
|
||||
|
||||
display_element.innerHTML = this.build_markup(
|
||||
trial.prompt,
|
||||
trial.button_label,
|
||||
trial.allow_refresh
|
||||
);
|
||||
|
||||
const canvas_element = display_element.querySelector(
|
||||
'.jspsych_captcha_canvas'
|
||||
);
|
||||
const canvas_context = canvas_element.getContext('2d');
|
||||
const input_element = display_element.querySelector(
|
||||
'.jspsych_captcha_input'
|
||||
);
|
||||
const button_element = display_element.querySelector(
|
||||
'.jspsych_captcha_submit'
|
||||
);
|
||||
const refresh_button = display_element.querySelector(
|
||||
'.jspsych_captcha_refresh'
|
||||
);
|
||||
const error_element = display_element.querySelector(
|
||||
'.jspsych_captcha_error'
|
||||
);
|
||||
|
||||
const draw_captcha = () => {
|
||||
this.draw_captcha(canvas_context, captcha_text, difficulty_settings);
|
||||
};
|
||||
|
||||
draw_captcha();
|
||||
input_element.focus();
|
||||
|
||||
if (refresh_button) {
|
||||
refresh_button.addEventListener('click', () => {
|
||||
attempt_counts.refresh_count += 1;
|
||||
captcha_text = this.generate_text(difficulty_settings.length);
|
||||
draw_captcha();
|
||||
error_element.textContent = '';
|
||||
input_element.value = '';
|
||||
input_element.focus();
|
||||
});
|
||||
}
|
||||
|
||||
const length_hint_element = display_element.querySelector(
|
||||
'.jspsych_captcha_length_hint'
|
||||
);
|
||||
|
||||
const update_button_state = () => {
|
||||
const current_length = input_element.value.trim().length;
|
||||
const required_length = captcha_text.length;
|
||||
button_element.disabled = current_length < required_length;
|
||||
if (length_hint_element) {
|
||||
length_hint_element.textContent =
|
||||
current_length < required_length
|
||||
? `Type ${required_length - current_length} more character(s) to continue.`
|
||||
: '';
|
||||
}
|
||||
};
|
||||
|
||||
const finish_trial = (response_value, is_correct) => {
|
||||
const trial_data = {
|
||||
rt: performance.now() - start_time,
|
||||
response: response_value,
|
||||
participant_entry: response_value,
|
||||
participant_entry_length: response_value.length,
|
||||
captcha_target: last_target_at_submission,
|
||||
correct: is_correct,
|
||||
submission_count: attempt_counts.submission_count,
|
||||
manual_refreshes: attempt_counts.refresh_count,
|
||||
difficulty_label: difficulty_settings.label,
|
||||
difficulty_length: difficulty_settings.length,
|
||||
difficulty_level: difficulty_settings.level,
|
||||
};
|
||||
display_element.innerHTML = '';
|
||||
this.jsPsych.finishTrial(trial_data);
|
||||
};
|
||||
|
||||
const validate_response = () => {
|
||||
attempt_counts.submission_count += 1;
|
||||
const response_value = input_element.value.trim().toUpperCase();
|
||||
last_target_at_submission = captcha_text;
|
||||
const is_correct = response_value === captcha_text;
|
||||
|
||||
if (response_value.length < captcha_text.length) {
|
||||
error_element.textContent = `Please enter all ${captcha_text.length} characters shown in the captcha.`;
|
||||
update_button_state();
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_correct || !trial.require_correct) {
|
||||
finish_trial(response_value, is_correct);
|
||||
return;
|
||||
}
|
||||
|
||||
error_element.textContent = trial.error_text;
|
||||
captcha_text = this.generate_text(difficulty_settings.length);
|
||||
draw_captcha();
|
||||
input_element.value = '';
|
||||
input_element.focus();
|
||||
};
|
||||
|
||||
button_element.addEventListener('click', validate_response);
|
||||
input_element.addEventListener('keydown', event => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
validate_response();
|
||||
}
|
||||
});
|
||||
input_element.addEventListener('input', update_button_state);
|
||||
update_button_state();
|
||||
}
|
||||
|
||||
build_markup(prompt, button_label, allow_refresh) {
|
||||
const prompt_html = prompt
|
||||
? `<div class="jspsych_captcha_prompt">${prompt}</div>`
|
||||
: '';
|
||||
const refresh_button = allow_refresh
|
||||
? '<button type="button" class="jspsych_captcha_refresh" aria-label="Get a new captcha">↻</button>'
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="jspsych_captcha">
|
||||
${prompt_html}
|
||||
<div class="jspsych_captcha_canvas_wrapper">
|
||||
<canvas class="jspsych_captcha_canvas" width="260" height="110" role="img" aria-label="Captcha"></canvas>
|
||||
${refresh_button}
|
||||
</div>
|
||||
<label class="jspsych_captcha_label" aria-label="Enter the characters you see into the textbox below.">
|
||||
<input type="text" autocomplete="off" autocorrect="off" autocapitalize="characters" spellcheck="false" class="jspsych_captcha_input" />
|
||||
<span class="jspsych_captcha_length_hint"></span>
|
||||
</label>
|
||||
<button type="button" class="jspsych-btn jspsych_captcha_submit">${button_label}</button>
|
||||
<div class="jspsych_captcha_error" role="status" aria-live="polite"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
draw_captcha(canvas_context, captcha_text, settings) {
|
||||
const { width, height } = canvas_context.canvas;
|
||||
canvas_context.clearRect(0, 0, width, height);
|
||||
const gradient = canvas_context.createLinearGradient(0, 0, width, height);
|
||||
gradient.addColorStop(0, '#f8fafc');
|
||||
gradient.addColorStop(1, '#e2e8f0');
|
||||
canvas_context.fillStyle = gradient;
|
||||
canvas_context.fillRect(0, 0, width, height);
|
||||
|
||||
for (let index = 0; index < settings.noise_lines; index++) {
|
||||
canvas_context.beginPath();
|
||||
canvas_context.moveTo(Math.random() * width, Math.random() * height);
|
||||
canvas_context.bezierCurveTo(
|
||||
Math.random() * width,
|
||||
Math.random() * height,
|
||||
Math.random() * width,
|
||||
Math.random() * height,
|
||||
Math.random() * width,
|
||||
Math.random() * height
|
||||
);
|
||||
canvas_context.lineWidth = 1 + Math.random() * 2;
|
||||
canvas_context.strokeStyle = `rgba(30, 41, 59, ${0.15 +
|
||||
Math.random() * 0.2})`;
|
||||
canvas_context.stroke();
|
||||
}
|
||||
|
||||
canvas_context.font = `bold ${settings.font_size}px 'Courier New', monospace`;
|
||||
canvas_context.textBaseline = 'middle';
|
||||
canvas_context.textAlign = 'center';
|
||||
|
||||
const horizontal_step = width / (captcha_text.length + 1);
|
||||
const center_y = height / 2;
|
||||
|
||||
for (let index = 0; index < captcha_text.length; index++) {
|
||||
const current_character = captcha_text[index];
|
||||
canvas_context.save();
|
||||
const jitter_y = this.random_between(-settings.jitter, settings.jitter);
|
||||
canvas_context.translate(
|
||||
horizontal_step * (index + 1),
|
||||
center_y + jitter_y
|
||||
);
|
||||
canvas_context.rotate(
|
||||
this.random_between(-settings.rotation, settings.rotation)
|
||||
);
|
||||
canvas_context.fillStyle = '#000000';
|
||||
canvas_context.fillText(current_character, 0, 0);
|
||||
canvas_context.restore();
|
||||
}
|
||||
|
||||
const noise_pixels = Math.floor(width * height * settings.speckle_density);
|
||||
const image_data = canvas_context.getImageData(0, 0, width, height);
|
||||
for (let index = 0; index < noise_pixels; index++) {
|
||||
const pixel_offset =
|
||||
(Math.floor(Math.random() * width) +
|
||||
Math.floor(Math.random() * height) * width) *
|
||||
4;
|
||||
const shade = Math.random() > 0.5 ? 255 : 0;
|
||||
image_data.data[pixel_offset] = shade;
|
||||
image_data.data[pixel_offset + 1] = shade;
|
||||
image_data.data[pixel_offset + 2] = shade;
|
||||
image_data.data[pixel_offset + 3] = 255;
|
||||
}
|
||||
canvas_context.putImageData(image_data, 0, 0);
|
||||
}
|
||||
|
||||
generate_text(length) {
|
||||
let generated_text = '';
|
||||
for (let index = 0; index < length; index++) {
|
||||
generated_text += this.characters.charAt(
|
||||
Math.floor(Math.random() * this.characters.length)
|
||||
);
|
||||
}
|
||||
return generated_text;
|
||||
}
|
||||
|
||||
resolve_difficulty(difficulty_input) {
|
||||
const default_level = 3;
|
||||
let normalized_level = Number(difficulty_input);
|
||||
if (!Number.isFinite(normalized_level)) {
|
||||
normalized_level = default_level;
|
||||
}
|
||||
normalized_level = Math.round(Math.min(6, Math.max(1, normalized_level)));
|
||||
|
||||
const base_length = 7;
|
||||
const base_font_size = 20;
|
||||
const base_rotation = 0.25;
|
||||
const max_rotation = base_rotation * 3;
|
||||
const base_noise_lines = 4;
|
||||
const base_speckle_density = 0.01;
|
||||
const base_jitter = 25;
|
||||
const noise_line_step = 0.8;
|
||||
const speckle_step = 0.0015;
|
||||
const jitter_step = 1.6;
|
||||
const rotation_step = (max_rotation - base_rotation) / 5;
|
||||
|
||||
const rotation = base_rotation + rotation_step * (normalized_level - 1);
|
||||
const noise_lines = Math.round(
|
||||
base_noise_lines + noise_line_step * (normalized_level - 1)
|
||||
);
|
||||
const speckle_density =
|
||||
base_speckle_density + speckle_step * (normalized_level - 1);
|
||||
const jitter = base_jitter + jitter_step * (normalized_level - 1);
|
||||
|
||||
return {
|
||||
length: base_length,
|
||||
noise_lines: noise_lines,
|
||||
rotation: rotation,
|
||||
jitter: jitter,
|
||||
font_size: base_font_size,
|
||||
speckle_density: speckle_density,
|
||||
label: `level_${normalized_level}`,
|
||||
level: normalized_level,
|
||||
};
|
||||
}
|
||||
|
||||
random_between(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
}
|
||||
|
||||
CaptchaPlugin.info = info;
|
||||
|
||||
export default CaptchaPlugin;
|
||||
209
plugins/jspsych-die-roll.js
Normal file
209
plugins/jspsych-die-roll.js
Normal file
@@ -0,0 +1,209 @@
|
||||
import { ParameterType } from 'jspsych';
|
||||
|
||||
const info = {
|
||||
name: 'die-roll',
|
||||
parameters: {
|
||||
prompt: {
|
||||
type: ParameterType.HTML_STRING,
|
||||
default: '',
|
||||
},
|
||||
rigged_value: {
|
||||
type: ParameterType.INT,
|
||||
default: null,
|
||||
},
|
||||
roll_duration: {
|
||||
type: ParameterType.INT,
|
||||
default: 2000,
|
||||
},
|
||||
animation_interval: {
|
||||
type: ParameterType.INT,
|
||||
default: 120,
|
||||
},
|
||||
button_label: {
|
||||
type: ParameterType.STRING,
|
||||
default: 'Continue',
|
||||
},
|
||||
result_template: {
|
||||
type: ParameterType.STRING,
|
||||
default: 'You rolled a {{value}}.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
class DieRollPlugin {
|
||||
constructor(jsPsych) {
|
||||
this.jsPsych = jsPsych;
|
||||
}
|
||||
|
||||
trial(display_element, trial) {
|
||||
const start_time = performance.now();
|
||||
const sanitized_rigged_value = this.get_rigged_value(trial.rigged_value);
|
||||
const final_value =
|
||||
sanitized_rigged_value ??
|
||||
this.jsPsych.randomization.sampleWithoutReplacement(
|
||||
[1, 2, 3, 4, 5, 6],
|
||||
1
|
||||
)[0];
|
||||
|
||||
display_element.innerHTML = this.build_markup(
|
||||
trial.prompt,
|
||||
trial.button_label
|
||||
);
|
||||
|
||||
const face_element = display_element.querySelector(
|
||||
'.jspsych_die_roll_face'
|
||||
);
|
||||
const result_element = display_element.querySelector(
|
||||
'.jspsych_die_roll_result'
|
||||
);
|
||||
const button_element = display_element.querySelector(
|
||||
'.jspsych_die_roll_button'
|
||||
);
|
||||
|
||||
let is_rolling = false;
|
||||
let has_rolled = false;
|
||||
let roll_started_at = null;
|
||||
let roll_stopped_at = null;
|
||||
let die_click_count = 0;
|
||||
|
||||
const clear_timeouts = () => {
|
||||
this.jsPsych.pluginAPI.clearAllTimeouts();
|
||||
};
|
||||
|
||||
const cycle_face = () => {
|
||||
if (!is_rolling) {
|
||||
return;
|
||||
}
|
||||
const value = this.random_face(final_value);
|
||||
face_element.textContent = value;
|
||||
this.jsPsych.pluginAPI.setTimeout(cycle_face, trial.animation_interval);
|
||||
};
|
||||
|
||||
const finalize_roll = () => {
|
||||
if (!is_rolling || has_rolled) {
|
||||
return;
|
||||
}
|
||||
is_rolling = false;
|
||||
has_rolled = true;
|
||||
die_click_count += 1;
|
||||
roll_stopped_at = performance.now();
|
||||
clear_timeouts();
|
||||
face_element.textContent = final_value;
|
||||
face_element.classList.remove('jspsych_die_roll_face--active');
|
||||
face_element.classList.remove('jspsych_die_roll_face--interactive');
|
||||
face_element.classList.add('jspsych_die_roll_face--locked');
|
||||
face_element.setAttribute('aria-disabled', 'true');
|
||||
face_element.removeAttribute('tabindex');
|
||||
face_element.style.pointerEvents = 'none';
|
||||
result_element.textContent = trial.result_template.replace(
|
||||
/\{\{value\}\}/gi,
|
||||
final_value
|
||||
);
|
||||
button_element.disabled = false;
|
||||
button_element.focus();
|
||||
};
|
||||
|
||||
const begin_roll = () => {
|
||||
if (is_rolling || has_rolled) {
|
||||
return;
|
||||
}
|
||||
is_rolling = true;
|
||||
die_click_count += 1;
|
||||
roll_started_at = performance.now();
|
||||
result_element.textContent = 'Rolling... click again to stop the die.';
|
||||
face_element.classList.add('jspsych_die_roll_face--active');
|
||||
button_element.disabled = true;
|
||||
clear_timeouts();
|
||||
cycle_face();
|
||||
};
|
||||
|
||||
const handle_face_interaction = () => {
|
||||
if (!is_rolling && !has_rolled) {
|
||||
begin_roll();
|
||||
} else if (is_rolling && !has_rolled) {
|
||||
finalize_roll();
|
||||
}
|
||||
};
|
||||
|
||||
button_element.disabled = true;
|
||||
face_element.textContent = '';
|
||||
result_element.textContent =
|
||||
'Click the die to start, then click it again to lock in your number.';
|
||||
face_element.classList.add('jspsych_die_roll_face--interactive');
|
||||
face_element.setAttribute('tabindex', '0');
|
||||
face_element.setAttribute('role', 'button');
|
||||
face_element.setAttribute(
|
||||
'aria-label',
|
||||
'Virtual die. Click to start and stop.'
|
||||
);
|
||||
|
||||
face_element.addEventListener('click', handle_face_interaction);
|
||||
face_element.addEventListener('keydown', event => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
handle_face_interaction();
|
||||
}
|
||||
});
|
||||
|
||||
const end_trial = () => {
|
||||
is_rolling = false;
|
||||
clear_timeouts();
|
||||
const trial_data = {
|
||||
roll: final_value,
|
||||
rigged_value: sanitized_rigged_value,
|
||||
rigged: sanitized_rigged_value !== null,
|
||||
die_click_count: die_click_count,
|
||||
roll_started_after_ms: roll_started_at
|
||||
? roll_started_at - start_time
|
||||
: null,
|
||||
roll_duration_ms:
|
||||
roll_started_at && roll_stopped_at
|
||||
? roll_stopped_at - roll_started_at
|
||||
: null,
|
||||
rt: performance.now() - start_time,
|
||||
};
|
||||
display_element.innerHTML = '';
|
||||
this.jsPsych.finishTrial(trial_data);
|
||||
};
|
||||
|
||||
button_element.addEventListener('click', () => {
|
||||
if (!button_element.disabled) {
|
||||
end_trial();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
build_markup(prompt, button_label) {
|
||||
const prompt_html = prompt
|
||||
? `<div class="jspsych_die_roll_prompt">${prompt}</div>`
|
||||
: '';
|
||||
return `
|
||||
<div class="jspsych_die_roll">
|
||||
${prompt_html}
|
||||
<div class="jspsych_die_roll_face">?</div>
|
||||
<div class="jspsych_die_roll_result" aria-live="polite"></div>
|
||||
<button class="jspsych-btn jspsych_die_roll_button" type="button">${button_label}</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
random_face(exclude_value) {
|
||||
let value = Math.floor(Math.random() * 6) + 1;
|
||||
if (value === exclude_value) {
|
||||
value = (value % 6) + 1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get_rigged_value(value) {
|
||||
const numeric = Number(value);
|
||||
if (Number.isFinite(numeric) && numeric >= 1 && numeric <= 6) {
|
||||
return Math.round(numeric);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
DieRollPlugin.info = info;
|
||||
|
||||
export default DieRollPlugin;
|
||||
@@ -9,13 +9,18 @@ export const textStimuli = {
|
||||
};
|
||||
|
||||
function getStimulusMap() {
|
||||
|
||||
const stimulusMap = new Map();
|
||||
|
||||
|
||||
stimulusMap.set('pre_survey_info', html`
|
||||
<p class="leading-relaxed">
|
||||
You will now answer a few questions about your experience in the experiment. It is important that you read the questions carefully and answer them honestly.
|
||||
stimulusMap.set(
|
||||
'pre_survey_info',
|
||||
html`
|
||||
<p class="leading-relaxed>
|
||||
The captcha task is now complete.
|
||||
</p>
|
||||
<p class="leading-relaxed mt-2"">
|
||||
You will now answer a few questions about your experience in the
|
||||
experiment. It is important that you read the questions carefully and
|
||||
answer them honestly.
|
||||
</p>
|
||||
<p class="leading-relaxed mt-2">
|
||||
Nonsense or random answers may lead to your submission being rejected.
|
||||
@@ -28,7 +33,6 @@ function getStimulusMap() {
|
||||
`
|
||||
);
|
||||
|
||||
|
||||
stimulusMap.set(
|
||||
'pre_consent_info',
|
||||
html`
|
||||
@@ -105,15 +109,16 @@ function getStimulusMap() {
|
||||
</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 University of Muenster, represented by the Rector, Prof. Dr.
|
||||
Johannes Wessels, Schlossplatz 2, 48149 MünsterTel.: + 49 251
|
||||
83-0E-Mail: verwaltung@uni-muenster.de
|
||||
states, as well as other data protection regulations is the University
|
||||
of Muenster, 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 University of Muenster is: Nina Meyer-Pachur
|
||||
Schlossplatz 2, 48149 Münster Tel.: + 49 251 83-22446 E-Mail:
|
||||
datenschutz@uni-muenster.de
|
||||
The data protection officer of the University of Muenster 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>
|
||||
@@ -159,14 +164,68 @@ function getStimulusMap() {
|
||||
`
|
||||
);
|
||||
|
||||
|
||||
stimulusMap.set(
|
||||
'instructions_1',
|
||||
html`
|
||||
TEST
|
||||
<p>In this experiment, you will be solving some captchas.</p>
|
||||
<p class="mt-2">
|
||||
This will involve identifying slightly distorted letters and numbers
|
||||
from an image.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
Your task is to solve the captchas as quickly and accurately as
|
||||
possible. You will not be given feedback on your performance.
|
||||
</p>
|
||||
<p class="mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'instructions_2',
|
||||
html`
|
||||
<p>
|
||||
To randomise the difficulty of the captchas you will be solving, we will
|
||||
first ask you to roll a virtual die. You will then be shown captchas of
|
||||
a difficulty corresponding to the number you rolled.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
The higher the number you roll, the more difficult the captchas will be.
|
||||
</p>
|
||||
<p class="mt-6">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
to continue.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'instructions_3',
|
||||
html`
|
||||
<p>
|
||||
You will now proceed to the captcha task, in which you will need to
|
||||
solve 12 captchas in a row as quickly and accurately as possible.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
Please ensure that you are in a quiet environment and that you will not
|
||||
be interrupted for the next few minutes.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
Please also ensure that you solve these captchas to the best of your
|
||||
ability. Nonsense or random answers may lead to your submission being
|
||||
rejected.
|
||||
</p>
|
||||
<p class="mt-6 text-red-500">
|
||||
Press
|
||||
<strong>SPACE</strong>
|
||||
when you are ready to begin.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
||||
stimulusMap.set(
|
||||
'debrief',
|
||||
@@ -184,10 +243,13 @@ function getStimulusMap() {
|
||||
</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 with your 'partner's' actions being peformed by a computer.
|
||||
the experiment – in reality, you performed the task alone with your
|
||||
'partner's' actions being peformed by a computer.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
You may have also read in the post-experiment questionnaire that your partner was either an AI agent – this was to examine whether people’s suspicions are biased by the way in which they are asked about them.
|
||||
You may have also read in the post-experiment questionnaire that your
|
||||
partner was either an AI agent – this was to examine whether people’s
|
||||
suspicions are biased by the way in which they are asked about them.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
Should you have any additional questions, you may contact Dr Shaheed
|
||||
|
||||
125
styles.css
125
styles.css
@@ -30,3 +30,128 @@
|
||||
width: 3vw;
|
||||
height: 3vw;
|
||||
}
|
||||
|
||||
.jspsych_die_roll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.jspsych_die_roll_prompt {
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
.jspsych_die_roll_face {
|
||||
width: 7.5rem;
|
||||
height: 7.5rem;
|
||||
border-radius: 1rem;
|
||||
border: 4px solid #0f172a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 3.5rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(145deg, #f1f5f9, #e2e8f0);
|
||||
box-shadow: 0 20px 35px rgba(15, 23, 42, 0.2);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: transform 120ms ease, box-shadow 120ms ease, opacity 120ms ease;
|
||||
}
|
||||
|
||||
.jspsych_die_roll_result {
|
||||
font-weight: 600;
|
||||
min-height: 1.5rem;
|
||||
}
|
||||
|
||||
.jspsych_die_roll_face--interactive:hover {
|
||||
transform: scale(1.03);
|
||||
box-shadow: 0 24px 40px rgba(15, 23, 42, 0.25);
|
||||
}
|
||||
|
||||
.jspsych_die_roll_face--active {
|
||||
animation: pulse 0.6s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
.jspsych_die_roll_face--locked {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
box-shadow: 0 15px 25px rgba(15, 23, 42, 0.15);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
from {
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.jspsych_captcha {
|
||||
max-width: 34rem;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jspsych_captcha_canvas_wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jspsych_captcha_canvas {
|
||||
border-radius: 0.75rem;
|
||||
border: 2px solid #cbd5f5;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.jspsych_captcha_refresh {
|
||||
border: none;
|
||||
background: #e2e8f0;
|
||||
border-radius: 9999px;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
font-size: 1.35rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jspsych_captcha_label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
font-weight: 600;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jspsych_captcha_input {
|
||||
border: 2px solid #cbd5f5;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: 0.2rem;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
max-width: 16rem;
|
||||
}
|
||||
|
||||
.jspsych_captcha_length_hint {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: #475569;
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
|
||||
.jspsych_captcha_error {
|
||||
min-height: 1.25rem;
|
||||
color: #b91c1c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user