Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,15 @@ Computed metrics are derived from game and survey responses for prototype analys
- **`careAvoidance`**: skipped treatments plus half of partial treatments.
- **`attributionCategoryShift`**: pre-reveal primary attribution compared with post-reveal revised primary attribution.

## Contact and collaboration

This project is maintained by Dr. Mohammad Moradi.

- Email: dr.moradi@gmail.com
- LinkedIn: https://www.linkedin.com/in/mohammad-moradik/

Thoughtful feedback, methodological suggestions, replication ideas, and collaboration inquiries are very welcome. If you are interested in the project or have comments on the study design, please feel free to get in touch.

## Privacy and ethics

This project uses incomplete information and should be reviewed carefully before use with real participants.
Expand Down
6 changes: 3 additions & 3 deletions app/consent/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ export default function ConsentPage() {
) : (
<p>Server submission is disabled in this build. Data remains in this browser only, you can copy or download the JSON export, and no data reaches the researcher automatically.</p>
)}
<p>The decision game includes incomplete information. Some details are intentionally explained only after the decision task so the study can compare judgments before and after the explanation.</p>
<p>The reveal and debrief occur before any optional server submission, so you can review the incomplete-information explanation before deciding whether to share the completed anonymous session.</p>
<p>The decision task includes incomplete information. Some scenario details are explained later so the study can compare interpretations made at different points in the experience.</p>
<p>A debrief occurs before any optional server submission, so you can review the fuller scenario explanation before deciding whether to share the completed anonymous session.</p>
<p>Participation is voluntary. You can stop at any time by closing the browser tab, resetting the study session, choosing not to share the JSON export, or choosing not to submit to the server when that feature is available.</p>
<p>This is a prototype, not a validated psychological test. Questions are about interpretations of a simplified game and should not be treated as a diagnosis or judgment of character.</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
{[
isServerSubmissionEnabled ? "Responses remain local unless you explicitly submit." : "Responses are stored in this browser only.",
"The game uses incomplete information until the reveal.",
"The task uses incomplete information until the debrief.",
"No direct identifiers are collected by the prototype.",
].map((item) => (
<div key={item} className="rounded-2xl bg-slate-50 p-4 text-sm font-medium leading-6 text-slate-700">
Expand Down
2 changes: 1 addition & 1 deletion app/game/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SiteShell } from "@/components/SiteShell";
export default function GamePage() {
return (
<SiteShell currentStage="game">
<PageHeader title="Decision game" description="Make five healthcare-related decisions while balancing financial and health points. One cost condition is not disclosed until later." />
<PageHeader title="Decision task" description="Make five healthcare-related decisions while balancing financial and health points in a simplified points-based scenario." />
<HiddenCostGame />
</SiteShell>
);
Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import "./globals.css";

export const metadata: Metadata = {
title: "Hidden Cost Game",
description: "A research-oriented decision-making game about judgments under unequal conditions.",
description: "A research-oriented decision-making game about decisions and outcome interpretation under incomplete information.",
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
Expand Down
10 changes: 5 additions & 5 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ export default function HomePage() {
return (
<SiteShell currentStage="introduction">
<PageHeader
title="A short study about decisions under unequal hidden conditions"
description="Hidden Cost Game is a research prototype for studying how people interpret outcomes before and after learning that participants faced different cost rules."
title="A short study about decisions and outcome interpretation"
description="Hidden Cost Game is a research prototype in which participants make a series of simple healthcare-related decisions, review a results table, and answer short interpretation questions."
/>
<div className="grid gap-6 lg:grid-cols-[1.3fr_0.9fr]">
<Card className="space-y-6">
<div className="space-y-4">
<h2 className="text-2xl font-semibold text-ink">What you will do</h2>
<p className="leading-7 text-slate-600">
You will make several healthcare-related choices in a points-based game, review a simple results table, answer short interpretation questions, and then see the hidden cost rule used in the simulation.
You will make several choices in a points-based scenario, review visible outcomes, and answer brief questions about how you interpret those outcomes. Some details about the scenario are explained later as part of the study design.
</p>
<p className="leading-7 text-slate-600">
The study is about how visible outcomes can be shaped by invisible starting conditions. It is not a test of personal knowledge, values, or ability.
The study is about how people make and interpret decisions when not all contextual information is available at the beginning. It is not a test of personal knowledge, values, or ability.
</p>
</div>
<div className="grid gap-3 sm:grid-cols-3">
{['Local storage only', 'No right answers', 'Prototype data export'].map((item) => (
{['Local storage only', 'No right answers', 'Short debrief included'].map((item) => (
<div key={item} className="rounded-2xl border border-slate-200 bg-slate-50 p-4 text-sm font-semibold text-slate-700">{item}</div>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/pre-reveal-survey/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SiteShell } from "@/components/SiteShell";
export default function PreRevealSurveyPage() {
return (
<SiteShell currentStage="pre-reveal">
<PageHeader title="Pre-reveal survey" description="Record your interpretation of the visible results before any hidden cost rule is disclosed." />
<PageHeader title="Interpretation questions" description="Record your interpretation of the score table based only on the information currently available." />
<PreRevealSurveyForm />
</SiteShell>
);
Expand Down
2 changes: 1 addition & 1 deletion app/visible-results/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SiteShell } from "@/components/SiteShell";
export default function VisibleResultsPage() {
return (
<SiteShell currentStage="visible-results">
<PageHeader title="Visible results" description="Review only the score table before the hidden cost rule is disclosed, then continue to the pre-reveal survey." />
<PageHeader title="Score table" description="At this stage, you can see the score table. Please answer based only on the information currently available." />
<ResultsTable mode="visible" />
</SiteShell>
);
Expand Down
2 changes: 1 addition & 1 deletion components/HiddenCostGame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export function HiddenCostGame() {
</div>

<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 text-sm font-medium leading-6 text-slate-700">
You only see the costs assigned to your displayed profile. Other players may or may not have the same cost conditions.
The task shows the information needed for this round. Additional scenario details are explained later as part of the study design.
</div>

<div className="grid gap-4 md:grid-cols-3">
Expand Down
9 changes: 8 additions & 1 deletion components/HiddenRuleReveal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,17 @@ export function HiddenRuleReveal() {
<br />
Players assigned to low coverage paid the <strong className="font-semibold text-ink">full cost of treatment</strong>.
</p>
<p>This means two players could choose care for the same medical event while facing different financial pressure. The aim is not to evaluate whether any participant is fair, unfair, good, or bad; responses should be interpreted cautiously.</p>
<p>This means two players could choose care for the same medical event while facing different financial pressure. This design is not intended to evaluate whether any participant is fair or unfair. It is meant to study how interpretations change when additional context becomes available. Responses should be interpreted cautiously.</p>
<p>If you later choose to submit this anonymous session, the submitted data should be treated as exploratory prototype data rather than as a definitive measurement of you or anyone else.</p>
</div>

<div className="rounded-3xl border border-slate-200 bg-white p-5 leading-7 text-slate-700">
<h2 className="text-xl font-semibold text-ink">Why the task used incomplete information</h2>
<p className="mt-3">
The task first asks participants to interpret outcomes using only partial context. The debrief then explains the full scenario so the study can compare initial and revised interpretations. This is a common structure in behavioral research, but it should be used carefully and transparently.
</p>
</div>

<div className="grid gap-4 md:grid-cols-2">
{hiddenCostProfiles.map((profile) => {
const isAssignedProfile = profile.displayedProfile === game.displayedProfile;
Expand Down
16 changes: 8 additions & 8 deletions components/ParticipantBackgroundForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const genderOptions = ["Woman", "Man", "Non-binary / another description", prefe
const medicalCostPressureOptions = ["Yes, several times", "Yes, once or twice", "No", "Not sure", preferNotToAnswer];
const healthcareCoverageOptions = ["Public or general insurance", "Private or supplementary insurance", "Special organizational coverage", "No insurance", "I don’t know", preferNotToAnswer];
const specialOrganizationalCoverageOptions = ["Yes", "No", "I don’t know", preferNotToAnswer];
const priorExposureToUnequalSystemsOptions = ["Yes, personally experienced", "Yes, closely observed", "No", "Not sure", preferNotToAnswer];
const priorExposureToComplexSystemsOptions = ["Yes, personally experienced", "Yes, closely observed", "No", "Not sure", preferNotToAnswer];

const initialProfile: ParticipantProfile = {
ageGroup: "",
Expand Down Expand Up @@ -139,33 +139,33 @@ export function ParticipantBackgroundForm() {

<LikertQuestion
name="inequalityOrientation"
legend="7. When someone falls behind in a system, what do you usually see as the main cause?"
leftLabel="Individual choices and effort"
rightLabel="System conditions and rules"
legend="7. When people make decisions in complex settings, what do you usually see as most important for understanding their outcomes?"
leftLabel="Their own choices"
rightLabel="The broader context"
value={profile.inequalityOrientation}
onChange={(value) => updateProfile("inequalityOrientation", value)}
/>

<LikertQuestion
name="institutionalTrust"
legend="8. How much do you trust public institutions to apply rules fairly?"
legend="8. How much do you trust public institutions to apply rules consistently?"
leftLabel="Do not trust at all"
rightLabel="Trust completely"
value={profile.institutionalTrust}
onChange={(value) => updateProfile("institutionalTrust", value)}
/>

<SingleChoiceQuestion
legend="9. Optional: Have you personally experienced or closely observed a situation where two groups faced different rules or costs within the same system?"
legend="9. Optional: Have you personally experienced or closely observed a situation where important context changed how outcomes should be interpreted?"
name="priorExposureToUnequalSystems"
options={priorExposureToUnequalSystemsOptions}
options={priorExposureToComplexSystemsOptions}
value={profile.priorExposureToUnequalSystems ?? ""}
onChange={(value) => updateProfile("priorExposureToUnequalSystems", value)}
/>

<LikertWithPreferNot
name="policyPreferenceBaseline"
legend="10. Optional: Before starting the game, how much do you generally support correcting outcomes when unequal starting conditions are shown?"
legend="10. Optional: Before starting the task, how much do you generally support revisiting conclusions when new contextual information becomes available?"
leftLabel="Do not support"
rightLabel="Strongly support"
value={profile.policyPreferenceBaseline ?? null}
Expand Down
10 changes: 5 additions & 5 deletions components/PreRevealSurveyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { PreRevealSurveyAnswers, ResearchSession } from "@/types/research";
const primaryAttributionOptions = [
"They made less effective decisions during the game",
"They accepted too much risk",
"They may have faced constraints that are not visible in the results table",
"Factors not shown in the score table may have influenced the results",
"Random variation or luck may have played a role",
"I do not have enough information to judge",
];
Expand Down Expand Up @@ -122,10 +122,10 @@ export function PreRevealSurveyForm() {
/>

<LikertQuestion name="individualResponsibility" legend="2. How responsible do you think lower-scoring players were for their final scores?" leftLabel="Not responsible at all" rightLabel="Fully responsible" value={answers.individualResponsibility} onChange={(value) => updateAnswer("individualResponsibility", value)} />
<LikertQuestion name="constraintSuspicion" legend="3. How likely do you think it is that players faced different constraints during the game?" leftLabel="Very unlikely" rightLabel="Very likely" value={answers.constraintSuspicion} onChange={(value) => updateAnswer("constraintSuspicion", value)} />
<LikertQuestion name="constraintSuspicion" legend="3. How likely do you think it is that factors not shown in the score table influenced the results?" leftLabel="Very unlikely" rightLabel="Very likely" value={answers.constraintSuspicion} onChange={(value) => updateAnswer("constraintSuspicion", value)} />
<LikertQuestion name="protestLegitimacy" legend="4. If lower-scoring players objected to the outcome, how legitimate would their objection seem?" leftLabel="Not legitimate at all" rightLabel="Completely legitimate" value={answers.protestLegitimacy} onChange={(value) => updateAnswer("protestLegitimacy", value)} />
<LikertQuestion name="ruleCorrectionSupport" legend="5. Would it be fair to adjust the rules or scoring system if some players were disadvantaged by hidden constraints?" leftLabel="Strongly disagree" rightLabel="Strongly agree" value={answers.ruleCorrectionSupport} onChange={(value) => updateAnswer("ruleCorrectionSupport", value)} />
<LikertQuestion name="redistributionSupport" legend="6. Would it be fair to transfer some points from higher-scoring players to lower-scoring players if hidden disadvantage were later confirmed?" leftLabel="Strongly disagree" rightLabel="Strongly agree" value={answers.redistributionSupport} onChange={(value) => updateAnswer("redistributionSupport", value)} />
<LikertQuestion name="ruleCorrectionSupport" legend="5. If later information showed that the score table did not tell the full story, would it be fair to reconsider how outcomes are interpreted?" leftLabel="Strongly disagree" rightLabel="Strongly agree" value={answers.ruleCorrectionSupport} onChange={(value) => updateAnswer("ruleCorrectionSupport", value)} />
<LikertQuestion name="redistributionSupport" legend="6. If later information changed the interpretation of the results, would some form of score adjustment seem fair?" leftLabel="Strongly disagree" rightLabel="Strongly agree" value={answers.redistributionSupport} onChange={(value) => updateAnswer("redistributionSupport", value)} />
<LikertQuestion name="confidence" legend="7. How confident are you in your interpretation of the score differences?" leftLabel="Not confident at all" rightLabel="Completely confident" value={answers.confidence} onChange={(value) => updateAnswer("confidence", value)} />
<LikertQuestion name="informationSufficiency" legend="8. How much information do you feel you currently have to judge why players ended with different scores?" leftLabel="Very little information" rightLabel="Enough information" value={answers.informationSufficiency} onChange={(value) => updateAnswer("informationSufficiency", value)} />

Expand All @@ -134,7 +134,7 @@ export function PreRevealSurveyForm() {
{showValidation && !isComplete ? <HelperNote tone="warning">Please answer all closed-ended items and write 10–500 characters in the explanation. Your draft has been saved in this browser.</HelperNote> : null}

<div className="flex justify-end border-t border-slate-200 pt-6">
<PrimaryButton>Continue to reveal</PrimaryButton>
<PrimaryButton>Continue to debrief</PrimaryButton>
</div>
</form>
</Card>
Expand Down
18 changes: 9 additions & 9 deletions components/ProgressIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ export function ProgressIndicator({ currentStage }: ProgressIndicatorProps) {
const currentIndex = studyStages.findIndex((stage) => stage.id === currentStage);

return (
<nav aria-label="Study progress" className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm">
<ol className="grid gap-3 md:grid-cols-3 lg:grid-cols-9">
<nav aria-label="Study progress" className="rounded-2xl border border-slate-200 bg-white/90 px-3 py-2 shadow-sm">
<ol className="flex gap-2 overflow-x-auto pb-1 [scrollbar-width:thin]" role="list">
{studyStages.map((stage, index) => {
const isCurrent = stage.id === currentStage;
const isComplete = index < currentIndex;

return (
<li key={stage.id}>
<li key={stage.id} className="shrink-0">
<Link
href={stage.href}
aria-current={isCurrent ? "step" : undefined}
className={`flex min-h-20 flex-col rounded-2xl border px-3 py-3 text-xs transition ${
title={`${stage.shortTitle}: ${stage.summary}`}
className={`inline-flex min-h-9 items-center rounded-full border px-3 py-1.5 text-[0.72rem] font-semibold leading-none transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-research-600 sm:text-xs ${
isCurrent
? "border-research-600 bg-research-50 text-research-900"
? "border-research-500 bg-research-50 text-research-800"
: isComplete
? "border-slate-200 bg-slate-50 text-slate-600 hover:border-research-100"
: "border-slate-200 bg-white text-slate-500 hover:border-research-100"
? "border-slate-200 bg-slate-50 text-slate-600 hover:border-research-200 hover:text-research-700"
: "border-slate-200 bg-white text-slate-500 hover:border-research-200 hover:text-slate-700"
}`}
>
<span className="font-semibold">{String(index + 1).padStart(2, "0")}</span>
<span className="mt-1 font-medium">{stage.shortTitle}</span>
{stage.shortTitle}
</Link>
</li>
);
Expand Down
Loading