working prototype

This commit is contained in:
2026-02-20 00:49:20 +01:00
parent 39ddf07973
commit 9b226d60da
5 changed files with 810 additions and 35 deletions

View File

@@ -8,13 +8,17 @@ import '@jspsych/plugin-survey/css/survey.css';
import './styles.css'; import './styles.css';
import { getStimulusMap } from './scripts/text-stimuli.js'; import { getStimulusMap } from './scripts/text-stimuli.js';
import { textStimuli } 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; const experiment_name = import.meta.env.VITE_EXPERIMENT_NAME;
let prolific_id; let prolific_id;
let probe_condition; // will be set to ai or human based on the condition. 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 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) { function delayed_redirect(url) {
setTimeout(() => { setTimeout(() => {
@@ -34,9 +38,14 @@ const jsPsych = initJsPsych({
console.log(jsPsych.data.get().json()); 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'); 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. ' + 'while the die roll was random, the difficulty of the captcha task was pre-determined and unrelated to the die roll. ' +
probe_closing_text; probe_closing_text;
let die_result = 3;
switch (COND) { switch (COND) {
case 0: case 0:
probe_condition = 'die'; probe_condition = 'die';
probe_order = 'die_first'; probe_order = 'die_first';
die_result = 4;
break; break;
case 1: case 1:
probe_condition = 'die'; probe_condition = 'die';
probe_order = 'die_first'; probe_order = 'die_first';
die_result = 4;
break; break;
case 2: case 2:
probe_condition = 'difficulty'; probe_condition = 'difficulty';
probe_order = 'die_first'; probe_order = 'die_first';
die_result = 4;
break; break;
case 3: case 3:
probe_condition = 'difficulty'; probe_condition = 'difficulty';
probe_order = 'die_first'; probe_order = 'die_first';
die_result = 4;
break; break;
case 4: case 4:
probe_condition = 'die'; probe_condition = 'die';
@@ -135,6 +150,18 @@ const instructions_1 = {
stimulus: stimulusMap.get('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 = { const debrief = {
type: jsPsychHtmlKeyboardResponse, type: jsPsychHtmlKeyboardResponse,
choices: [' '], choices: [' '],
@@ -147,6 +174,40 @@ const pre_survey_info = {
stimulus: stimulusMap.get('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 = { const die_probe_row = {
text: text:
'I suspected that the die roll was not random, or the number I received was pre-determined.', '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.`, text: `I found the captcha task difficult.`,
value: 'Difficulty', 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.`, text: `I think I solved all the captchas correctly.`,
value: 'Accuracy', 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) { if (debug) {
timeline.push(survey); timeline.push(...main_experiment_timeline);
timeline.push(debrief);
} }
if (!debug) { if (!debug) {
timeline.push(pre_consent_info); timeline.push(pre_consent_info);
timeline.push(consent_form); timeline.push(consent_form);
timeline.push(enter_fullscreen); timeline.push(enter_fullscreen);
timeline.push(instructions_1); timeline.push(...main_experiment_timeline);
timeline.push(pre_survey_info);
timeline.push(survey);
timeline.push(debrief);
} }
jsPsych.run(timeline); jsPsych.run(timeline);

303
plugins/jspsych-captcha.js Normal file
View 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
View 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;

View File

@@ -9,15 +9,20 @@ export const textStimuli = {
}; };
function getStimulusMap() { function getStimulusMap() {
const stimulusMap = new Map(); const stimulusMap = new Map();
stimulusMap.set(
stimulusMap.set('pre_survey_info', html` 'pre_survey_info',
<p class="leading-relaxed"> html`
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 class="leading-relaxed>
The captcha task is now complete.
</p> </p>
<p class ="leading-relaxed mt-2"> <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. Nonsense or random answers may lead to your submission being rejected.
</p> </p>
<p class="mt-6"> <p class="mt-6">
@@ -28,7 +33,6 @@ function getStimulusMap() {
` `
); );
stimulusMap.set( stimulusMap.set(
'pre_consent_info', 'pre_consent_info',
html` html`
@@ -105,15 +109,16 @@ function getStimulusMap() {
</p> </p>
The controller within the meaning of the EU General Data Protection The controller within the meaning of the EU General Data Protection
Regulation (GDPR) and other national data protection laws of the member 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. states, as well as other data protection regulations is the University
Johannes Wessels, Schlossplatz 2, 48149 MünsterTel.: + 49 251 of Muenster, represented by the Rector, Prof. Dr. Johannes Wessels,
83-0E-Mail: verwaltung@uni-muenster.de Schlossplatz 2, 48149 MünsterTel.: + 49 251 83-0E-Mail:
verwaltung@uni-muenster.de
<p class="font-semibold mt-2"> <p class="font-semibold mt-2">
8. Contact details of the data protection officer 8. Contact details of the data protection officer
</p> </p>
The data protection officer of the University of Muenster is: Nina Meyer-Pachur The data protection officer of the University of Muenster is: Nina
Schlossplatz 2, 48149 Münster Tel.: + 49 251 83-22446 E-Mail: Meyer-Pachur Schlossplatz 2, 48149 Münster Tel.: + 49 251 83-22446
datenschutz@uni-muenster.de E-Mail: datenschutz@uni-muenster.de
<p class="font-semibold mt-2"> <p class="font-semibold mt-2">
9. Reference to the rights of those affected 9. Reference to the rights of those affected
</p> </p>
@@ -159,14 +164,68 @@ function getStimulusMap() {
` `
); );
stimulusMap.set( stimulusMap.set(
'instructions_1', 'instructions_1',
html` 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( stimulusMap.set(
'debrief', 'debrief',
@@ -184,10 +243,13 @@ function getStimulusMap() {
</p> </p>
<p class="mt-2"> <p class="mt-2">
To this end, we created the appearance of additional participants in 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>
<p class="mt-2"> <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 peoples 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 peoples
suspicions are biased by the way in which they are asked about them.
</p> </p>
<p class="mt-2"> <p class="mt-2">
Should you have any additional questions, you may contact Dr Shaheed Should you have any additional questions, you may contact Dr Shaheed

View File

@@ -30,3 +30,128 @@
width: 3vw; width: 3vw;
height: 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;
}