2471 lines
73 KiB
Markdown
2471 lines
73 KiB
Markdown
|
||
This project implements a **Visual Paired Associates Memory (VPAM) Test** frontend application for psychological research. The test measures associative memory, encoding, retention, consolidation, and recognition abilities by teaching participants picture-picture pairs and testing recall at multiple time points.
|
||
|
||
## Response Requirements
|
||
|
||
**Every AI response must start with:**
|
||
"Inshallah lets start"
|
||
|
||
**Modal Identification:**
|
||
At the beginning of each response, identify the current AI model being used by stating its official name.
|
||
|
||
---
|
||
|
||
## 🎯 Core Test Specifications
|
||
|
||
### Test Structure (CRITICAL)
|
||
- **Total Pairs:** 35 unique picture-picture pairs
|
||
- **Encoding Phase:** Learn all 35 pairs (one-time exposure)
|
||
- **Immediate Cued Recall:** Test immediately after encoding (35 trials)
|
||
- **Delay Interval:** 15 minutes with filler tasks
|
||
- **Delayed Cued Recall:** Test after delay (35 trials)
|
||
- **Recognition Phase:** Multiple choice recognition (35 trials)
|
||
- **Total Time:** Approximately 30-35 minutes
|
||
|
||
### Age Groups
|
||
|
||
1. **Adolescents (14-18 years):**
|
||
- Exposure time: 3.5 seconds per pair
|
||
- Recall response time: 15 seconds per trial
|
||
- Recognition response time: 4 seconds per trial
|
||
- Colorful gradient background
|
||
- Friendly, encouraging instructions
|
||
- Pairs: Pen+Kite, Butterfly+Spoon, Elephant+Mango, etc. (35 pairs)
|
||
|
||
2. **Adults (18-22 years):**
|
||
- Exposure time: 2.5 seconds per pair
|
||
- Recall response time: 15 seconds per trial
|
||
- Recognition response time: 4 seconds per trial
|
||
- Soft neutral background
|
||
- Professional, concise instructions
|
||
- Pairs: Mixer+Spectacles, Mobile Phone+Water Tap, Lemon+Socks, etc. (35 pairs)
|
||
|
||
---
|
||
|
||
## 🔑 Critical Implementation Rules
|
||
|
||
### Phase 1: Encoding (Learning Phase)
|
||
|
||
```javascript
|
||
// ENCODING RULES:
|
||
// 1. Display 35 pairs sequentially
|
||
// 2. Each pair shown for 3.5s (adolescents) or 2.5s (adults)
|
||
// 3. NO response required - passive viewing only
|
||
// 4. Pairs displayed in WHITE BOX, centered on screen
|
||
// 5. Automatic progression to next pair
|
||
// 6. After last pair → immediately transition to Immediate Recall
|
||
|
||
// Pair Display Format:
|
||
// [Image 1] + [Image 2]
|
||
// Side by side or stacked (specify in design)
|
||
```
|
||
|
||
**Adolescent Pairs (EXACT ORDER from PDF):**
|
||
```javascript
|
||
[
|
||
{ first: 'Pen', second: 'Kite' },
|
||
{ first: 'Butterfly', second: 'Spoon' },
|
||
{ first: 'Elephant', second: 'Mango' },
|
||
{ first: 'Lock', second: 'Flute' },
|
||
{ first: 'Notebook', second: 'Cow' },
|
||
{ first: 'Eraser', second: 'Cup' },
|
||
{ first: 'Tomato', second: 'Bag' },
|
||
{ first: 'Heart', second: 'Scissors' },
|
||
{ first: 'Drum', second: 'Goat' },
|
||
{ first: 'Phone', second: 'Auto' },
|
||
{ first: 'Table', second: 'Parrot' },
|
||
{ first: 'Robot', second: 'Chair' },
|
||
{ first: 'Ship', second: 'Potato' },
|
||
{ first: 'Pencil', second: 'Dice' },
|
||
{ first: 'Belt', second: 'Lemon' },
|
||
{ first: 'Hibiscus', second: 'Bike' },
|
||
{ first: 'Carrot', second: 'Peacock' },
|
||
{ first: 'Candle', second: 'Balloon' },
|
||
{ first: 'Fork', second: 'Bulb' },
|
||
{ first: 'Sharpener', second: 'Lotus' },
|
||
{ first: 'Shirt', second: 'Banana' },
|
||
{ first: 'Chain', second: 'Jug' },
|
||
{ first: 'Hand', second: 'Rat' },
|
||
{ first: 'Television', second: 'Coconut' },
|
||
{ first: 'Book', second: 'Key' },
|
||
{ first: 'Shoe', second: 'Ears' },
|
||
{ first: 'Dog', second: 'Crown' },
|
||
{ first: 'Cloud', second: 'Mirror' },
|
||
{ first: 'Cycle', second: 'Plate' },
|
||
{ first: 'Pig', second: 'Clock' },
|
||
{ first: 'Watch', second: 'Crayon' },
|
||
{ first: 'Leaf', second: 'Pant' },
|
||
{ first: 'Door', second: 'Apple' },
|
||
{ first: 'Hat', second: 'Ring' },
|
||
{ first: 'Bucket', second: 'Monkey' }
|
||
]
|
||
```
|
||
|
||
**Adult Pairs (EXACT ORDER from PDF):**
|
||
```javascript
|
||
[
|
||
{ first: 'Mixer', second: 'Spectacles' },
|
||
{ first: 'Mobile Phone', second: 'Water Tap' },
|
||
{ first: 'Lemon', second: 'Socks' },
|
||
{ first: 'Plane', second: 'Lamp' },
|
||
{ first: 'Pillow', second: 'Mug' },
|
||
{ first: 'Bowl', second: 'Switch' },
|
||
{ first: 'Helmet', second: 'Rose' },
|
||
{ first: 'Shark', second: 'Fan' },
|
||
{ first: 'Sun', second: 'Table' },
|
||
{ first: 'Rock', second: 'Peas' },
|
||
{ first: 'Chilli', second: 'Hen' },
|
||
{ first: 'Window', second: 'Orange' },
|
||
{ first: 'Purse', second: 'Hammer' },
|
||
{ first: 'Zebra', second: 'Almonds' },
|
||
{ first: 'Ball', second: 'Nose' },
|
||
{ first: 'Rope', second: 'Ice Cream' },
|
||
{ first: 'Horse', second: 'Umbrella' },
|
||
{ first: 'Nose', second: 'Cauliflower' },
|
||
{ first: 'Pen', second: 'Chair' },
|
||
{ first: 'Clock', second: 'Dustbin' },
|
||
{ first: 'Laptop', second: 'Moon' },
|
||
{ first: 'Crocodile', second: 'Brush' },
|
||
{ first: 'Cup', second: 'Rainbow' },
|
||
{ first: 'Scooter', second: 'Calendar' },
|
||
{ first: 'Camel', second: 'Key' },
|
||
{ first: 'Bank', second: 'Watermelon' },
|
||
{ first: 'Jug', second: 'Beetle' },
|
||
{ first: 'Iron Box', second: 'Strawberry' },
|
||
{ first: 'Sunflower', second: 'Ladder' },
|
||
{ first: 'Screwdriver', second: 'Garlic' },
|
||
{ first: 'Basket', second: 'Train' },
|
||
{ first: 'Camera', second: 'Starfish' },
|
||
{ first: 'Lady\'s Finger', second: 'Candle' },
|
||
{ first: 'Torch Light', second: 'Mat' },
|
||
{ first: 'Cherry', second: 'Mirror' }
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 2: Immediate Cued Recall
|
||
|
||
```javascript
|
||
// IMMEDIATE RECALL RULES:
|
||
// 1. Show ONE item from each learned pair as CUE
|
||
// 2. Participant types the MATCHING item
|
||
// 3. 15 seconds response window per trial
|
||
// 4. Text input field below cue image
|
||
// 5. Automatic advance on submit OR timeout
|
||
// 6. Show "Time is up!" for 1 second on timeout
|
||
// 7. NO feedback on correctness during test
|
||
// 8. Order: Show SECOND item, ask for FIRST (or vice versa)
|
||
|
||
// Response Validation:
|
||
// - Must match EXACT item name (case-insensitive)
|
||
// - Don't accept categories ("animal" vs "Elephant")
|
||
// - Don't accept descriptors ("red apple" vs "Apple")
|
||
// - Trim whitespace, ignore punctuation
|
||
```
|
||
|
||
**Adolescent Immediate Recall Cues (EXACT ORDER):**
|
||
```javascript
|
||
[
|
||
{ cue: 'Kite', correctAnswer: 'Pen' },
|
||
{ cue: 'Butterfly', correctAnswer: 'Spoon' },
|
||
{ cue: 'Mango', correctAnswer: 'Elephant' },
|
||
{ cue: 'Flute', correctAnswer: 'Lock' },
|
||
{ cue: 'Notebook', correctAnswer: 'Cow' },
|
||
{ cue: 'Eraser', correctAnswer: 'Cup' },
|
||
{ cue: 'Bag', correctAnswer: 'Tomato' },
|
||
{ cue: 'Heart', correctAnswer: 'Scissors' },
|
||
{ cue: 'Drum', correctAnswer: 'Goat' },
|
||
{ cue: 'Auto', correctAnswer: 'Phone' },
|
||
{ cue: 'Parrot', correctAnswer: 'Table' },
|
||
{ cue: 'Robot', correctAnswer: 'Chair' },
|
||
{ cue: 'Potato', correctAnswer: 'Ship' },
|
||
{ cue: 'Dice', correctAnswer: 'Pencil' },
|
||
{ cue: 'Belt', correctAnswer: 'Lemon' },
|
||
{ cue: 'Bike', correctAnswer: 'Hibiscus' },
|
||
{ cue: 'Peacock', correctAnswer: 'Carrot' },
|
||
{ cue: 'Balloon', correctAnswer: 'Candle' },
|
||
{ cue: 'Bulb', correctAnswer: 'Fork' },
|
||
{ cue: 'Sharpener', correctAnswer: 'Lotus' },
|
||
{ cue: 'Banana', correctAnswer: 'Shirt' },
|
||
{ cue: 'Jug', correctAnswer: 'Chain' },
|
||
{ cue: 'Rat', correctAnswer: 'Hand' },
|
||
{ cue: 'Coconut', correctAnswer: 'Television' },
|
||
{ cue: 'Key', correctAnswer: 'Book' },
|
||
{ cue: 'Ears', correctAnswer: 'Shoe' },
|
||
{ cue: 'Dog', correctAnswer: 'Crown' },
|
||
{ cue: 'Mirror', correctAnswer: 'Cloud' },
|
||
{ cue: 'Plate', correctAnswer: 'Cycle' },
|
||
{ cue: 'Clock', correctAnswer: 'Pig' },
|
||
{ cue: 'Crayon', correctAnswer: 'Watch' },
|
||
{ cue: 'Pant', correctAnswer: 'Leaf' },
|
||
{ cue: 'Apple', correctAnswer: 'Door' },
|
||
{ cue: 'Ring', correctAnswer: 'Hat' },
|
||
{ cue: 'Monkey', correctAnswer: 'Bucket' }
|
||
]
|
||
```
|
||
|
||
**Adult Immediate Recall Cues (EXACT ORDER):**
|
||
```javascript
|
||
[
|
||
{ cue: 'Spectacles', correctAnswer: 'Mixer' },
|
||
{ cue: 'Water Tap', correctAnswer: 'Mobile Phone' },
|
||
{ cue: 'Lemon', correctAnswer: 'Socks' },
|
||
{ cue: 'Plane', correctAnswer: 'Lamp' },
|
||
{ cue: 'Mug', correctAnswer: 'Pillow' },
|
||
{ cue: 'Switch', correctAnswer: 'Bowl' },
|
||
{ cue: 'Rose', correctAnswer: 'Helmet' },
|
||
{ cue: 'Shark', correctAnswer: 'Fan' },
|
||
{ cue: 'Table', correctAnswer: 'Sun' },
|
||
{ cue: 'Peas', correctAnswer: 'Rock' },
|
||
{ cue: 'Hen', correctAnswer: 'Chilli' },
|
||
{ cue: 'Orange', correctAnswer: 'Window' },
|
||
{ cue: 'Purse', correctAnswer: 'Hammer' },
|
||
{ cue: 'Zebra', correctAnswer: 'Almonds' },
|
||
{ cue: 'Nose', correctAnswer: 'Ball' },
|
||
{ cue: 'Ice Cream', correctAnswer: 'Rope' },
|
||
{ cue: 'Umbrella', correctAnswer: 'Horse' },
|
||
{ cue: 'Cauliflower', correctAnswer: 'Nose' },
|
||
{ cue: 'Chair', correctAnswer: 'Pen' },
|
||
{ cue: 'Dustbin', correctAnswer: 'Clock' },
|
||
{ cue: 'Moon', correctAnswer: 'Laptop' },
|
||
{ cue: 'Brush', correctAnswer: 'Crocodile' },
|
||
{ cue: 'Rainbow', correctAnswer: 'Cup' },
|
||
{ cue: 'Calendar', correctAnswer: 'Scooter' },
|
||
{ cue: 'Camel', correctAnswer: 'Key' },
|
||
{ cue: 'Watermelon', correctAnswer: 'Bank' },
|
||
{ cue: 'Beetle', correctAnswer: 'Jug' },
|
||
{ cue: 'Strawberry', correctAnswer: 'Iron Box' },
|
||
{ cue: 'Ladder', correctAnswer: 'Sunflower' },
|
||
{ cue: 'Garlic', correctAnswer: 'Screwdriver' },
|
||
{ cue: 'Train', correctAnswer: 'Basket' },
|
||
{ cue: 'Starfish', correctAnswer: 'Camera' },
|
||
{ cue: 'Candle', correctAnswer: 'Lady\'s Finger' },
|
||
{ cue: 'Mat', correctAnswer: 'Torch Light' },
|
||
{ cue: 'Mirror', correctAnswer: 'Cherry' }
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 3: Delay Interval (15 Minutes)
|
||
|
||
```javascript
|
||
// DELAY INTERVAL RULES:
|
||
// 1. Duration: EXACTLY 15 minutes
|
||
// 2. Filler tasks administered sequentially:
|
||
// a. Response Inhibition Task (Go/No-Go)
|
||
// b. Cognitive Flexibility Task
|
||
// c. Stroop Task (Attention)
|
||
// 3. These tasks prevent rehearsal of learned pairs
|
||
// 4. After 15 minutes → automatically launch Delayed Recall
|
||
// 5. Do NOT allow participant to skip or pause
|
||
|
||
// Transition Message:
|
||
// Adolescents: "Great job looking at all the pairs! Before we test
|
||
// your memory, you'll play a few short brain-teaser games..."
|
||
// Adults: "Well done—you've completed the first part. Next, you'll do
|
||
// a few short thinking activities designed to keep your mind active..."
|
||
```
|
||
|
||
**Filler Task Integration:**
|
||
- Import existing RIT component
|
||
- Import existing CFT component
|
||
- Import existing Stroop component (to be developed)
|
||
- Chain them sequentially
|
||
- Track total elapsed time
|
||
- Trigger Delayed Recall at exactly 15:00 minutes
|
||
|
||
---
|
||
|
||
### Phase 4: Delayed Cued Recall
|
||
|
||
```javascript
|
||
// DELAYED RECALL RULES:
|
||
// 1. Identical to Immediate Recall in format
|
||
// 2. Show OPPOSITE cue from immediate recall
|
||
// - If immediate showed SECOND → now show FIRST
|
||
// - If immediate showed FIRST → now show SECOND
|
||
// 3. Same 15-second response window
|
||
// 4. Same text input validation
|
||
// 5. "Time is up!" message on timeout
|
||
// 6. NO feedback on correctness
|
||
|
||
// Purpose: Measure retention and forgetting over time
|
||
// Compare performance: Delayed vs Immediate
|
||
```
|
||
|
||
**Adolescent Delayed Recall Cues (EXACT ORDER):**
|
||
```javascript
|
||
[
|
||
{ cue: 'Pen', correctAnswer: 'Kite' },
|
||
{ cue: 'Spoon', correctAnswer: 'Butterfly' },
|
||
{ cue: 'Elephant', correctAnswer: 'Mango' },
|
||
{ cue: 'Lock', correctAnswer: 'Flute' },
|
||
{ cue: 'Cow', correctAnswer: 'Notebook' },
|
||
{ cue: 'Cup', correctAnswer: 'Eraser' },
|
||
{ cue: 'Tomato', correctAnswer: 'Bag' },
|
||
{ cue: 'Scissors', correctAnswer: 'Heart' },
|
||
{ cue: 'Goat', correctAnswer: 'Drum' },
|
||
{ cue: 'Phone', correctAnswer: 'Auto' },
|
||
{ cue: 'Table', correctAnswer: 'Parrot' },
|
||
{ cue: 'Chair', correctAnswer: 'Robot' },
|
||
{ cue: 'Ship', correctAnswer: 'Potato' },
|
||
{ cue: 'Pencil', correctAnswer: 'Dice' },
|
||
{ cue: 'Lemon', correctAnswer: 'Belt' },
|
||
{ cue: 'Hibiscus', correctAnswer: 'Bike' },
|
||
{ cue: 'Carrot', correctAnswer: 'Peacock' },
|
||
{ cue: 'Candle', correctAnswer: 'Balloon' },
|
||
{ cue: 'Fork', correctAnswer: 'Bulb' },
|
||
{ cue: 'Lotus', correctAnswer: 'Sharpener' },
|
||
{ cue: 'Shirt', correctAnswer: 'Banana' },
|
||
{ cue: 'Chain', correctAnswer: 'Jug' },
|
||
{ cue: 'Hand', correctAnswer: 'Rat' },
|
||
{ cue: 'Television', correctAnswer: 'Coconut' },
|
||
{ cue: 'Book', correctAnswer: 'Key' },
|
||
{ cue: 'Shoe', correctAnswer: 'Ears' },
|
||
{ cue: 'Crown', correctAnswer: 'Dog' },
|
||
{ cue: 'Cloud', correctAnswer: 'Mirror' },
|
||
{ cue: 'Cycle', correctAnswer: 'Plate' },
|
||
{ cue: 'Pig', correctAnswer: 'Clock' },
|
||
{ cue: 'Watch', correctAnswer: 'Crayon' },
|
||
{ cue: 'Leaf', correctAnswer: 'Pant' },
|
||
{ cue: 'Door', correctAnswer: 'Apple' },
|
||
{ cue: 'Hat', correctAnswer: 'Ring' },
|
||
{ cue: 'Bucket', correctAnswer: 'Monkey' }
|
||
]
|
||
```
|
||
|
||
**Adult Delayed Recall Cues (EXACT ORDER):**
|
||
```javascript
|
||
[
|
||
{ cue: 'Mixer', correctAnswer: 'Spectacles' },
|
||
{ cue: 'Mobile Phone', correctAnswer: 'Water Tap' },
|
||
{ cue: 'Socks', correctAnswer: 'Lemon' },
|
||
{ cue: 'Lamp', correctAnswer: 'Plane' },
|
||
{ cue: 'Pillow', correctAnswer: 'Mug' },
|
||
{ cue: 'Bowl', correctAnswer: 'Switch' },
|
||
{ cue: 'Helmet', correctAnswer: 'Rose' },
|
||
{ cue: 'Fan', correctAnswer: 'Shark' },
|
||
{ cue: 'Sun', correctAnswer: 'Table' },
|
||
{ cue: 'Rock', correctAnswer: 'Peas' },
|
||
{ cue: 'Chilli', correctAnswer: 'Hen' },
|
||
{ cue: 'Window', correctAnswer: 'Orange' },
|
||
{ cue: 'Hammer', correctAnswer: 'Purse' },
|
||
{ cue: 'Almonds', correctAnswer: 'Zebra' },
|
||
{ cue: 'Ball', correctAnswer: 'Nose' },
|
||
{ cue: 'Rope', correctAnswer: 'Ice Cream' },
|
||
{ cue: 'Horse', correctAnswer: 'Umbrella' },
|
||
{ cue: 'Nose', correctAnswer: 'Cauliflower' },
|
||
{ cue: 'Pen', correctAnswer: 'Chair' },
|
||
{ cue: 'Clock', correctAnswer: 'Dustbin' },
|
||
{ cue: 'Laptop', correctAnswer: 'Moon' },
|
||
{ cue: 'Crocodile', correctAnswer: 'Brush' },
|
||
{ cue: 'Cup', correctAnswer: 'Rainbow' },
|
||
{ cue: 'Scooter', correctAnswer: 'Calendar' },
|
||
{ cue: 'Key', correctAnswer: 'Camel' },
|
||
{ cue: 'Bank', correctAnswer: 'Watermelon' },
|
||
{ cue: 'Jug', correctAnswer: 'Beetle' },
|
||
{ cue: 'Iron Box', correctAnswer: 'Strawberry' },
|
||
{ cue: 'Sunflower', correctAnswer: 'Ladder' },
|
||
{ cue: 'Screwdriver', correctAnswer: 'Garlic' },
|
||
{ cue: 'Basket', correctAnswer: 'Train' },
|
||
{ cue: 'Camera', correctAnswer: 'Starfish' },
|
||
{ cue: 'Candle', correctAnswer: 'Lady\'s Finger' },
|
||
{ cue: 'Torch Light', correctAnswer: 'Mat' },
|
||
{ cue: 'Cherry', correctAnswer: 'Mirror' }
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 5: Recognition Phase
|
||
|
||
```javascript
|
||
// RECOGNITION RULES:
|
||
// 1. Show cue image at top in white box
|
||
// 2. Display THREE options horizontally below:
|
||
// a. Correct target (the actual paired item)
|
||
// b. Related distractor (same category as target)
|
||
// c. Unrelated distractor (different category)
|
||
// 3. RANDOMIZE option positions each trial
|
||
// 4. 4-second response window (FAST!)
|
||
// 5. Click/tap to select
|
||
// 6. Immediate advance on selection
|
||
// 7. "Too Late" message for 1 second on timeout
|
||
// 8. NO feedback on correctness
|
||
|
||
// Distractor Selection Logic:
|
||
// - Related: Same semantic category (animal→animal, food→food)
|
||
// - Unrelated: Completely different category
|
||
// - Balance distractor difficulty across trials
|
||
```
|
||
|
||
**Adolescent Recognition Trials (EXACT ORDER with distractors):**
|
||
```javascript
|
||
[
|
||
{
|
||
cue: 'Pen',
|
||
correctTarget: 'Kite',
|
||
relatedDistractor: 'Parachute',
|
||
unrelatedDistractor: 'Elephant'
|
||
},
|
||
{
|
||
cue: 'Butterfly',
|
||
correctTarget: 'Spoon',
|
||
relatedDistractor: 'Knife',
|
||
unrelatedDistractor: 'Scooter'
|
||
},
|
||
{
|
||
cue: 'Mango',
|
||
correctTarget: 'Elephant',
|
||
relatedDistractor: 'Cow',
|
||
unrelatedDistractor: 'Phone'
|
||
},
|
||
// ... (all 35 trials with distractors from PDF)
|
||
]
|
||
```
|
||
|
||
**Adult Recognition Trials (EXACT ORDER with distractors):**
|
||
```javascript
|
||
[
|
||
{
|
||
cue: 'Mixer',
|
||
correctTarget: 'Spectacles',
|
||
relatedDistractor: 'Sunglasses',
|
||
unrelatedDistractor: 'Apple'
|
||
},
|
||
{
|
||
cue: 'Mobile Phone',
|
||
correctTarget: 'Water Tap',
|
||
relatedDistractor: 'Shower',
|
||
unrelatedDistractor: 'Cow'
|
||
},
|
||
{
|
||
cue: 'Lemon',
|
||
correctTarget: 'Socks',
|
||
relatedDistractor: 'Shoe',
|
||
unrelatedDistractor: 'Mango'
|
||
},
|
||
// ... (all 35 trials with distractors from PDF)
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## 📱 Screen Flow (Sequential Development)
|
||
|
||
### Development Order (ONE SCREEN AT A TIME)
|
||
|
||
#### Screen 1: IntroScreen
|
||
**Purpose:** Welcome participants and select age group
|
||
|
||
**Components needed:**
|
||
- Welcome header with test title
|
||
- Test overview description
|
||
- Age group selector (14-18 vs 18-22)
|
||
- "Let's Go!" button to start
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/DelayedRecallInstructionScreen.jsx`
|
||
|
||
**Instructions:**
|
||
|
||
Adolescents:
|
||
```
|
||
"Welcome back to the memory game you played earlier! Remember, you saw
|
||
pairs of items and tried to learn which ones go together. Now, again, one
|
||
item from each pair will appear alone. Your task is to write the item that
|
||
goes with it. You'll have a short time for each one, so try to answer quickly.
|
||
If you don't respond in time, the game will say 'Time is up!' and move on
|
||
to the next item.
|
||
|
||
Write the exact item that goes with the one you see. Don't just write a
|
||
general category, color, or size of the object — try to remember the
|
||
specific pair.
|
||
|
||
Example: If you see 🍎 Apple and it was paired with 🪑 Chair, write 'Chair',
|
||
not 'furniture' or 'brown chair' or 'big chair'."
|
||
```
|
||
|
||
Adults:
|
||
```
|
||
"We're returning to the memory task you did before. Earlier, you learned
|
||
pairs of items and tried to remember which ones belonged together. In this
|
||
part, again, a single item from each pair will appear alone. Your task is
|
||
to type or write the item that was originally paired with it. You will have
|
||
limited time to respond for each item. If you do not answer within the time
|
||
limit, the message 'Time is up!' will appear and the task will move on.
|
||
|
||
Type the exact item that was paired with the cue you see. Do not write a
|
||
general category, color, or size of the object — focus on the specific
|
||
paired item.
|
||
|
||
Example: If the cue is 🖊️ Pen and it was paired with 🐘 Elephant, type
|
||
'Elephant', not 'animal', 'creature', or 'grey elephant' or 'big elephant'."
|
||
```
|
||
|
||
---
|
||
|
||
#### Screen 8: DelayedRecallScreen
|
||
**Purpose:** Test memory retention after 15-minute delay
|
||
|
||
**Components needed:**
|
||
- Same as ImmediateRecallScreen
|
||
- Reuse RecallInput component
|
||
- Different cue sequence
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/DelayedRecallScreen.jsx`
|
||
- Reuse: `components/shared/VPAM/RecallInput.jsx`
|
||
- Extend: `hooks/vpam/useRecallScreen.js`
|
||
|
||
**Logic:**
|
||
- Identical to Immediate Recall
|
||
- Use OPPOSITE cue from immediate recall
|
||
- Same 15-second response window
|
||
- Same validation rules
|
||
- Track all same metrics
|
||
- After trial 35 → transition to Recognition
|
||
|
||
---
|
||
|
||
#### Screen 9: RecognitionInstructionScreen
|
||
**Purpose:** Explain multiple choice recognition task
|
||
|
||
**Components needed:**
|
||
- Instruction text
|
||
- Visual example of 3-option layout
|
||
- Speed reminder (4 seconds!)
|
||
- "Let's Go!" button
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/RecognitionInstructionScreen.jsx`
|
||
|
||
**Instructions:**
|
||
|
||
Adolescents:
|
||
```
|
||
"Next, you'll see one picture at the top of the screen. Below it, you'll
|
||
see three choices. Your job is to pick the picture that was originally
|
||
paired with the one on top. Be quick—you'll only have a few seconds for
|
||
each choice. If you don't tap the suitable choice on time, the game will
|
||
say 'Too Late!' and move on."
|
||
```
|
||
|
||
Adults:
|
||
```
|
||
"In this final part, you'll again see one item at the top of the screen.
|
||
Below it, three choices will be shown. Select the item that was originally
|
||
paired with the one on top. Be ready—each trial only lasts a few seconds.
|
||
If you don't tap the suitable choice on time, the game will say 'Too Late!'
|
||
and move on."
|
||
```
|
||
|
||
---
|
||
|
||
#### Screen 10: RecognitionScreen
|
||
**Purpose:** Multiple choice recognition test
|
||
|
||
**Components needed:**
|
||
- Cue image display (top, white box)
|
||
- Three option buttons (horizontal layout)
|
||
- Click/tap detection
|
||
- Timer (4 seconds)
|
||
- "Too Late" message overlay
|
||
- Trial counter
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/RecognitionScreen.jsx`
|
||
- `components/shared/VPAM/RecognitionOptions.jsx`
|
||
- `hooks/vpam/useRecognitionScreen.js`
|
||
|
||
**Critical logic:**
|
||
```javascript
|
||
// For each trial:
|
||
1. Display cue image at top
|
||
2. Randomize 3 option positions:
|
||
- Correct target
|
||
- Related distractor
|
||
- Unrelated distractor
|
||
3. Start 4-second timer
|
||
4. Listen for option selection
|
||
5. On selection:
|
||
- Highlight selected option briefly
|
||
- Record choice and RT
|
||
- Validate accuracy
|
||
- Advance immediately
|
||
6. On timeout:
|
||
- Show "Too Late" for 1 second
|
||
- Record as no response (accuracy = 0)
|
||
- Advance to next trial
|
||
7. After trial 35 → transition to Results
|
||
```
|
||
|
||
**Option randomization:**
|
||
```javascript
|
||
function randomizeOptions(correct, related, unrelated) {
|
||
const options = [
|
||
{ id: 'correct', image: correct, isCorrect: true },
|
||
{ id: 'related', image: related, isCorrect: false },
|
||
{ id: 'unrelated', image: unrelated, isCorrect: false }
|
||
];
|
||
|
||
// Fisher-Yates shuffle
|
||
for (let i = options.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[options[i], options[j]] = [options[j], options[i]];
|
||
}
|
||
|
||
return options;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### Screen 11: ResultsScreen
|
||
**Purpose:** Display performance metrics and export data
|
||
|
||
**Components needed:**
|
||
- Performance summary cards
|
||
- Accuracy breakdown by phase
|
||
- Reaction time statistics
|
||
- Consolidation slope visualization
|
||
- Data export buttons (JSON, CSV)
|
||
- "Finish" or "Return to Test Selection" button
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/ResultsScreen.jsx`
|
||
- `components/shared/VPAM/MetricsCard.jsx`
|
||
- `components/shared/VPAM/PhaseComparison.jsx`
|
||
- `utils/vpamCalculations.js`
|
||
- `utils/vpamDataExport.js`
|
||
|
||
**Metrics to display:**
|
||
|
||
```javascript
|
||
// Accuracy Metrics (%)
|
||
1. Immediate Recall Accuracy
|
||
2. Delayed Recall Accuracy
|
||
3. Recognition Accuracy
|
||
4. Overall Task Accuracy
|
||
|
||
// Response Metrics
|
||
5. Total Responses Given
|
||
6. Total No Responses (timeouts)
|
||
7. Correct Responses (all phases)
|
||
8. Incorrect Responses (all phases)
|
||
|
||
// Reaction Time (ms)
|
||
9. Mean RT - Immediate Recall
|
||
10. Mean RT - Delayed Recall
|
||
11. Mean RT - Recognition
|
||
12. Overall Mean RT
|
||
|
||
// Memory Change
|
||
13. Consolidation Slope (Absolute)
|
||
= Delayed Accuracy - Immediate Accuracy
|
||
14. Consolidation Slope (%)
|
||
= (Delayed - Immediate) / Immediate × 100
|
||
```
|
||
|
||
**Completion messages:**
|
||
|
||
Adolescents:
|
||
```
|
||
"That's it! You've finished this memory game. Great work!"
|
||
```
|
||
|
||
Adults:
|
||
```
|
||
"This concludes the memory task. Thank you for your participation."
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 Design Requirements
|
||
|
||
### Visual Design Principles
|
||
- **Clean, focused interface** - minimize cognitive load
|
||
- **Age-appropriate aesthetics:**
|
||
- Adolescents: Colorful gradient background, playful fonts
|
||
- Adults: Soft neutral background, professional fonts
|
||
- **Responsive:** 345px to large screens
|
||
- **Accessibility:** High contrast, clear typography
|
||
- **Minimal animations** - don't distract from memory task
|
||
|
||
### Technology Stack
|
||
- **Framework:** React with JavaScript (JSX)
|
||
- **Styling:** Tailwind CSS only (no custom CSS files)
|
||
- **Animations:** Framer Motion (minimal, subtle only)
|
||
- **State Management:** React Hooks (useState, useEffect, useRef)
|
||
- **NO localStorage/sessionStorage** - use in-memory state only
|
||
- **NO external image hosting** - images in public/images/vpam/
|
||
|
||
### Image Requirements
|
||
|
||
**Image specifications:**
|
||
```
|
||
Location: public/images/vpam/
|
||
Format: PNG or SVG
|
||
Size: ~200x200px (consistent dimensions)
|
||
Quality: High-res but optimized (<50KB each)
|
||
Background: Transparent or white
|
||
Naming: lowercase, hyphens (e.g., magic-potion.png)
|
||
|
||
Total images needed:
|
||
- Adolescents: 70 unique images (35 pairs × 2)
|
||
- Adults: 70 unique images (35 pairs × 2)
|
||
- Distractors: ~70 additional images for recognition
|
||
|
||
Total: ~210 images
|
||
```
|
||
|
||
**Image organization:**
|
||
```
|
||
public/images/vpam/
|
||
├── adolescent/
|
||
│ ├── pen.png
|
||
│ ├── kite.png
|
||
│ ├── butterfly.png
|
||
│ └── ... (70 items)
|
||
├── adult/
|
||
│ ├── mixer.png
|
||
│ ├── spectacles.png
|
||
│ ├── mobile-phone.png
|
||
│ └── ... (70 items)
|
||
└── distractors/
|
||
├── parachute.png
|
||
├── knife.png
|
||
└── ... (70 items)
|
||
```
|
||
|
||
### Stimulus Display Specifications
|
||
|
||
**Encoding Phase:**
|
||
```javascript
|
||
// Display format:
|
||
<div className="bg-white border-4 border-gray-300 rounded-lg p-8
|
||
flex items-center justify-center gap-8">
|
||
<img src={firstImage} className="w-40 h-40 object-contain" />
|
||
<img src={secondImage} className="w-40 h-40 object-contain" />
|
||
</div>
|
||
|
||
// Or stacked for mobile:
|
||
<div className="flex flex-col gap-4">
|
||
<img src={firstImage} className="w-32 h-32 object-contain" />
|
||
<img src={secondImage} className="w-32 h-32 object-contain" />
|
||
</div>
|
||
```
|
||
|
||
**Recall Phase:**
|
||
```javascript
|
||
// Cue display:
|
||
<div className="bg-white border-4 border-gray-300 rounded-lg p-8
|
||
flex items-center justify-center">
|
||
<img src={cueImage} className="w-48 h-48 object-contain" />
|
||
</div>
|
||
|
||
// Input field:
|
||
<input
|
||
type="text"
|
||
className="w-full max-w-md px-4 py-3 text-lg border-2
|
||
border-gray-400 rounded-lg focus:border-blue-500
|
||
focus:ring-2 focus:ring-blue-200"
|
||
placeholder="Type the paired item..."
|
||
maxLength="50"
|
||
/>
|
||
```
|
||
|
||
**Recognition Phase:**
|
||
```javascript
|
||
// Cue at top:
|
||
<div className="mb-8">
|
||
<img src={cueImage} className="w-32 h-32 object-contain mx-auto" />
|
||
</div>
|
||
|
||
// Three options (horizontal):
|
||
<div className="flex flex-wrap justify-center gap-4">
|
||
{options.map(option => (
|
||
<button
|
||
className="border-4 border-gray-300 rounded-lg p-4
|
||
hover:border-blue-400 active:bg-blue-50
|
||
transition-colors"
|
||
onClick={() => handleSelection(option)}
|
||
>
|
||
<img src={option.image} className="w-32 h-32 object-contain" />
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
// Selected state:
|
||
<button className="border-4 border-blue-500 bg-blue-50 ring-4 ring-blue-200">
|
||
<img src={selectedOption.image} />
|
||
</button>
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 Data Recording Requirements
|
||
|
||
### Trial Data Structure
|
||
|
||
Every trial must record:
|
||
```javascript
|
||
{
|
||
// Identification
|
||
trialNumber: number, // 1-105 (35 encoding + 35 immediate + 35 delayed + 35 recognition)
|
||
phase: 'encoding' | 'immediate_recall' | 'delayed_recall' | 'recognition',
|
||
trialInPhase: number, // 1-35 per phase
|
||
|
||
// Stimulus information
|
||
cueItem: string, // 'Kite', 'Spectacles', etc.
|
||
targetItem: string, // 'Pen', 'Mixer', etc.
|
||
distractorRelated: string | null, // 'Parachute' (recognition only)
|
||
distractorUnrelated: string | null, // 'Elephant' (recognition only)
|
||
|
||
// Response data
|
||
participantResponse: string | null, // Typed text or selected option
|
||
responseTime: number | null, // milliseconds from stimulus onset
|
||
responseAccuracy: 0 | 1, // 1 = correct, 0 = incorrect/timeout
|
||
responseType: 'typed' | 'selected' | 'timeout' | 'no-response',
|
||
|
||
// Recognition specific
|
||
selectedOption: 'correct' | 'related' | 'unrelated' | null,
|
||
optionPositions: array | null, // [0, 1, 2] randomized order
|
||
|
||
// Timing
|
||
stimulusOnsetTime: number, // timestamp
|
||
responseSubmitTime: number | null, // timestamp
|
||
encodingDuration: number | null, // encoding phase only
|
||
|
||
// Metadata
|
||
ageGroup: 'adolescent' | 'adult',
|
||
timestamp: number // Date.now()
|
||
}
|
||
```
|
||
|
||
### Summary Data Structure
|
||
|
||
After test completion:
|
||
```javascript
|
||
{
|
||
// Participant info
|
||
participantInfo: {
|
||
ageGroup: 'adolescent' | 'adult',
|
||
testDate: string,
|
||
testStartTime: string,
|
||
testEndTime: string,
|
||
totalDuration: number, // minutes
|
||
delayDurationActual: number // should be ~15 minutes
|
||
},
|
||
|
||
// Accuracy metrics (%)
|
||
accuracy: {
|
||
immediateRecall: number,
|
||
delayedRecall: number,
|
||
recognition: number,
|
||
overall: number
|
||
},
|
||
|
||
// Response counts
|
||
responseCounts: {
|
||
totalTrials: 105,
|
||
totalResponses: number,
|
||
totalTimeouts: number,
|
||
correctResponses: number,
|
||
incorrectResponses: number,
|
||
|
||
// By phase
|
||
immediateRecallAnswered: number,
|
||
delayedRecallAnswered: number,
|
||
recognitionAnswered: number
|
||
},
|
||
|
||
// Reaction time statistics (ms)
|
||
reactionTimes: {
|
||
immediateRecallMean: number,
|
||
delayedRecallMean: number,
|
||
recognitionMean: number,
|
||
overallMean: number,
|
||
|
||
// Additional stats
|
||
immediateRecallSD: number,
|
||
delayedRecallSD: number,
|
||
recognitionSD: number
|
||
},
|
||
|
||
// Memory consolidation
|
||
consolidation: {
|
||
absoluteChange: number, // Delayed - Immediate
|
||
percentageChange: number, // (Delayed - Immediate) / Immediate × 100
|
||
forgettingRate: number, // Items forgotten
|
||
itemsRetained: number // Correct in both phases
|
||
},
|
||
|
||
// Phase-specific accuracy rates
|
||
phaseAccuracy: {
|
||
immediateRecall: {
|
||
correct: number,
|
||
incorrect: number,
|
||
timeout: number,
|
||
accuracy: number // %
|
||
},
|
||
delayedRecall: {
|
||
correct: number,
|
||
incorrect: number,
|
||
timeout: number,
|
||
accuracy: number // %
|
||
},
|
||
recognition: {
|
||
correct: number,
|
||
relatedDistractorChosen: number,
|
||
unrelatedDistractorChosen: number,
|
||
timeout: number,
|
||
accuracy: number // %
|
||
}
|
||
},
|
||
|
||
// Raw trial data
|
||
trialData: [/* array of all trial records */]
|
||
}
|
||
```
|
||
|
||
### Data Export Formats
|
||
|
||
**JSON Export:**
|
||
```javascript
|
||
// Filename: vpam_results_[ageGroup]_[timestamp].json
|
||
// Full data structure with all nested objects
|
||
```
|
||
|
||
**CSV Export:**
|
||
```javascript
|
||
// Filename: vpam_results_[ageGroup]_[timestamp].csv
|
||
// Flattened trial data with column headers:
|
||
// trial_number, phase, trial_in_phase, cue_item, target_item,
|
||
// participant_response, response_time, accuracy, ...
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 Key Constants to Define
|
||
|
||
### vpamConfig.js
|
||
|
||
```javascript
|
||
export const VPAM_CONFIG = {
|
||
TOTAL_PAIRS: 35,
|
||
PHASES: ['encoding', 'immediate_recall', 'delayed_recall', 'recognition'],
|
||
|
||
TIMING: {
|
||
// Encoding
|
||
ENCODING_DURATION_ADOLESCENT: 3500, // ms
|
||
ENCODING_DURATION_ADULT: 2500, // ms
|
||
INTER_PAIR_INTERVAL: 300, // ms blank screen between pairs
|
||
|
||
// Recall phases
|
||
RECALL_RESPONSE_WINDOW: 15000, // ms (both age groups)
|
||
TIMEOUT_MESSAGE_DURATION: 1000, // ms
|
||
|
||
// Recognition phase
|
||
RECOGNITION_RESPONSE_WINDOW: 4000, // ms (both age groups)
|
||
TOO_LATE_MESSAGE_DURATION: 1000, // ms
|
||
|
||
// Delay interval
|
||
DELAY_DURATION: 900000, // ms (15 minutes)
|
||
},
|
||
|
||
FILLER_TASKS: [
|
||
'response_inhibition',
|
||
'cognitive_flexibility',
|
||
'stroop' // if available
|
||
],
|
||
|
||
VALIDATION: {
|
||
MAX_INPUT_LENGTH: 50,
|
||
CASE_SENSITIVE: false,
|
||
TRIM_WHITESPACE: true,
|
||
ALLOW_TYPOS: false // set to true for lenient matching
|
||
}
|
||
};
|
||
```
|
||
|
||
### vpamInstructions.js
|
||
|
||
```javascript
|
||
export const VPAM_INSTRUCTIONS = {
|
||
adolescent: {
|
||
intro: {
|
||
title: "Memory Game: Picture Pairs",
|
||
description: `In this game, you will see pictures of everyday things.
|
||
Some of these pictures will be shown together as pairs. Your task is
|
||
to pay close attention and try to remember which ones are being
|
||
presented as pairs, because later we will test your memory in different
|
||
ways. Sometimes you will have to write the missing item, and sometimes
|
||
you will choose it from a few options. Let's get started!`,
|
||
buttonText: "Let's Go!"
|
||
},
|
||
|
||
encoding: {
|
||
title: "Learning Phase",
|
||
description: `You will see two pictures appear inside a box on the screen.
|
||
Just look carefully and try to remember which pictures are being
|
||
presented as pairs. You don't need to press anything. Each pair will
|
||
disappear after a few seconds, so focus while it is on the screen.`,
|
||
buttonText: "Let's Go!"
|
||
},
|
||
|
||
immediateRecall: {
|
||
title: "Memory Test: Part 1",
|
||
description: `Great job learning the pairs! Now, one item from each pair
|
||
will appear by itself. Your task is to write the item that goes with it.
|
||
Try to answer quickly — you'll have a short time for each one.`,
|
||
reminder: `Remember: write the exact item that was paired. Don't just
|
||
write a general category, color, or size — focus on the specific item.`,
|
||
example: `Example: If you see 🍎 Apple and it was paired with 🪑 Chair,
|
||
write 'Chair', not 'furniture' or 'brown chair' or 'big chair'.`,
|
||
buttonText: "Let's Go!"
|
||
},
|
||
|
||
delayTransition: {
|
||
title: "Brain Break!",
|
||
description: `Great job looking at all the pairs! Before we test your
|
||
memory, you'll play a few short brain-teaser games. These games are
|
||
like a quick workout for your mind. Once you're done, we'll bring you
|
||
back to check how many pairs you can remember!`,
|
||
buttonText: "Let's Go!"
|
||
},
|
||
|
||
delayedRecall: {
|
||
title: "Memory Test: Part 2",
|
||
description: `Welcome back to the memory game you played earlier! Remember,
|
||
you saw pairs of items and tried to learn which ones go together. Now,
|
||
again, one item from each pair will appear alone. Your task is to write
|
||
the item that goes with it. You'll have a short time for each one, so
|
||
try to answer quickly.`,
|
||
reminder: `Write the exact item that goes with the one you see. Don't just
|
||
write a general category, color, or size of the object — try to remember
|
||
the specific pair.`,
|
||
buttonText: "Let's Go!"
|
||
},
|
||
|
||
recognition: {
|
||
title: "Memory Test: Final Part",
|
||
description: `Next, you'll see one picture at the top of the screen. Below
|
||
it, you'll see three choices. Your job is to pick the picture that was
|
||
originally paired with the one on top. Be quick—you'll only have a few
|
||
seconds for each choice.`,
|
||
buttonText: "Let's Go!"
|
||
},
|
||
|
||
completion: {
|
||
title: "Awesome Work!",
|
||
message: "That's it! You've finished this memory game. Great work!",
|
||
buttonText: "View Results"
|
||
}
|
||
},
|
||
|
||
adult: {
|
||
intro: {
|
||
title: "Visual Paired Associates Memory Test",
|
||
description: `In this task, you will see images of everyday objects.
|
||
Some of these images will be presented together as pairs. Your task is
|
||
to observe carefully and remember which items are paired together, as
|
||
your memory will be tested in different ways. You will sometimes need
|
||
to type the missing item, and other times select it from multiple options.`,
|
||
buttonText: "Begin Task"
|
||
},
|
||
|
||
encoding: {
|
||
title: "Learning Phase",
|
||
description: `You will see two items presented together inside a box. Your
|
||
task is to observe carefully and remember which items are being presented
|
||
as pairs. You don't need to press anything. Each pair will only remain on
|
||
screen briefly, so focus while it is shown.`,
|
||
buttonText: "Start Learning"
|
||
},
|
||
|
||
immediateRecall: {
|
||
title: "Immediate Recall Test",
|
||
description: `You've just seen all the pairs. Now, a single item from each
|
||
pair will appear alone. Your task is to type or write the item that was
|
||
originally paired with it. Respond quickly — each trial has a time limit.`,
|
||
reminder: `Type the exact item that was paired. Do not write a general
|
||
category, color, or size — focus on the specific item.`,
|
||
example: `Example: If the cue is 🖊️ Pen and it was paired with 🐘 Elephant,
|
||
type 'Elephant', not 'animal', 'creature', or 'grey elephant'.`,
|
||
buttonText: "Begin Recall"
|
||
},
|
||
|
||
delayTransition: {
|
||
title: "Interim Activities",
|
||
description: `Well done—you've completed the first part. Next, you'll do a
|
||
few short thinking activities designed to keep your mind active while your
|
||
memory continues to work in the background. After that, we'll return to
|
||
test how well you remember the pairs.`,
|
||
buttonText: "Continue"
|
||
},
|
||
|
||
delayedRecall: {
|
||
title: "Delayed Recall Test",
|
||
description: `We're returning to the memory task you did before. Earlier,
|
||
you learned pairs of items and tried to remember which ones belonged
|
||
together. In this part, again, a single item from each pair will appear
|
||
alone. Your task is to type or write the item that was originally paired
|
||
with it.`,
|
||
reminder: `Type the exact item that was paired with the cue you see. Do not
|
||
write a general category, color, or size of the object — focus on the
|
||
specific paired item.`,
|
||
buttonText: "Begin Test"
|
||
},
|
||
|
||
recognition: {
|
||
title: "Recognition Test",
|
||
description: `In this final part, you'll again see one item at the top of
|
||
the screen. Below it, three choices will be shown. Select the item that
|
||
was originally paired with the one on top. Be ready—each trial only lasts
|
||
a few seconds.`,
|
||
buttonText: "Begin Recognition"
|
||
},
|
||
|
||
completion: {
|
||
title: "Task Complete",
|
||
message: "This concludes the memory task. Thank you for your participation.",
|
||
buttonText: "View Results"
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
### vpamPairs.js
|
||
|
||
```javascript
|
||
// EXACT sequences from PDF
|
||
export const VPAM_PAIRS = {
|
||
adolescent: [
|
||
{ id: 1, first: 'Pen', second: 'Kite', firstImg: 'pen.png', secondImg: 'kite.png' },
|
||
{ id: 2, first: 'Butterfly', second: 'Spoon', firstImg: 'butterfly.png', secondImg: 'spoon.png' },
|
||
// ... all 35 pairs
|
||
],
|
||
|
||
adult: [
|
||
{ id: 1, first: 'Mixer', second: 'Spectacles', firstImg: 'mixer.png', secondImg: 'spectacles.png' },
|
||
{ id: 2, first: 'Mobile Phone', second: 'Water Tap', firstImg: 'mobile-phone.png', secondImg: 'water-tap.png' },
|
||
// ... all 35 pairs
|
||
]
|
||
};
|
||
|
||
// Immediate recall cues (show SECOND, ask for FIRST)
|
||
export const IMMEDIATE_RECALL_CUES = {
|
||
adolescent: [
|
||
{ id: 1, cue: 'Kite', cueImg: 'kite.png', correctAnswer: 'Pen', correctImg: 'pen.png' },
|
||
{ id: 2, cue: 'Butterfly', cueImg: 'butterfly.png', correctAnswer: 'Spoon', correctImg: 'spoon.png' },
|
||
// ... all 35 trials
|
||
],
|
||
|
||
adult: [
|
||
{ id: 1, cue: 'Spectacles', cueImg: 'spectacles.png', correctAnswer: 'Mixer', correctImg: 'mixer.png' },
|
||
// ... all 35 trials
|
||
]
|
||
};
|
||
|
||
// Delayed recall cues (show FIRST, ask for SECOND)
|
||
export const DELAYED_RECALL_CUES = {
|
||
adolescent: [
|
||
{ id: 1, cue: 'Pen', cueImg: 'pen.png', correctAnswer: 'Kite', correctImg: 'kite.png' },
|
||
// ... all 35 trials
|
||
],
|
||
|
||
adult: [
|
||
{ id: 1, cue: 'Mixer', cueImg: 'mixer.png', correctAnswer: 'Spectacles', correctImg: 'spectacles.png' },
|
||
// ... all 35 trials
|
||
]
|
||
};
|
||
|
||
// Recognition trials with distractors
|
||
export const RECOGNITION_TRIALS = {
|
||
adolescent: [
|
||
{
|
||
id: 1,
|
||
cue: 'Pen',
|
||
cueImg: 'pen.png',
|
||
correctTarget: 'Kite',
|
||
correctImg: 'kite.png',
|
||
relatedDistractor: 'Parachute',
|
||
relatedImg: 'parachute.png',
|
||
unrelatedDistractor: 'Elephant',
|
||
unrelatedImg: 'elephant.png'
|
||
},
|
||
// ... all 35 trials with distractors
|
||
],
|
||
|
||
adult: [
|
||
{
|
||
id: 1,
|
||
cue: 'Mixer',
|
||
cueImg: 'mixer.png',
|
||
correctTarget: 'Spectacles',
|
||
correctImg: 'spectacles.png',
|
||
relatedDistractor: 'Sunglasses',
|
||
relatedImg: 'sunglasses.png',
|
||
unrelatedDistractor: 'Apple',
|
||
unrelatedImg: 'apple.png'
|
||
},
|
||
// ... all 35 trials
|
||
]
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 📁 File Structure Requirements
|
||
|
||
```
|
||
src/
|
||
├── components/
|
||
│ ├── screens/
|
||
│ │ ├── VPAM/
|
||
│ │ │ ├── IntroScreen.jsx
|
||
│ │ │ ├── EncodingInstructionScreen.jsx
|
||
│ │ │ ├── EncodingScreen.jsx
|
||
│ │ │ ├── ImmediateRecallInstructionScreen.jsx
|
||
│ │ │ ├── ImmediateRecallScreen.jsx
|
||
│ │ │ ├── DelayIntervalScreen.jsx
|
||
│ │ │ ├── DelayedRecallInstructionScreen.jsx
|
||
│ │ │ ├── DelayedRecallScreen.jsx
|
||
│ │ │ ├── RecognitionInstructionScreen.jsx
|
||
│ │ │ ├── RecognitionScreen.jsx
|
||
│ │ │ ├── ResultsScreen.jsx
|
||
│ │ │ └── VisualPairedAssociatesTest.jsx (main orchestrator)
|
||
│ │ │
|
||
│ │ ├── CFT/ # Existing
|
||
│ │ ├── RIT/ # Existing
|
||
│ │ └── TestSelectionScreen.jsx
|
||
│ │
|
||
│ └── shared/
|
||
│ └── VPAM/
|
||
│ ├── PairDisplay.jsx
|
||
│ ├── RecallInput.jsx
|
||
│ ├── RecognitionOptions.jsx
|
||
│ ├── TrialCounter.jsx
|
||
│ ├── Timer.jsx
|
||
│ ├── MetricsCard.jsx
|
||
│ └── PhaseComparison.jsx
|
||
│
|
||
├── hooks/
|
||
│ └── vpam/
|
||
│ ├── useEncodingScreen.js
|
||
│ ├── useRecallScreen.js
|
||
│ ├── useRecognitionScreen.js
|
||
│ └── useDelayInterval.js
|
||
│
|
||
├── utils/
|
||
│ ├── vpamTimer.js
|
||
│ ├── vpamValidation.js
|
||
│ ├── vpamCalculations.js
|
||
│ ├── vpamDataExport.js
|
||
│ └── vpamAnimations.js
|
||
│
|
||
├── constants/
|
||
│ ├── vpamConfig.js
|
||
│ ├── vpamInstructions.js
|
||
│ ├── vpamPairs.js
|
||
│ └── testsConfig.js (update to include VPAM)
|
||
│
|
||
└── public/
|
||
└── images/
|
||
└── vpam/
|
||
├── adolescent/
|
||
│ ├── pen.png
|
||
│ ├── kite.png
|
||
│ └── ... (70 images)
|
||
├── adult/
|
||
│ ├── mixer.png
|
||
│ ├── spectacles.png
|
||
│ └── ... (70 images)
|
||
└── distractors/
|
||
├── parachute.png
|
||
├── knife.png
|
||
└── ... (70 images)
|
||
```
|
||
|
||
---
|
||
|
||
## 🚫 Important Constraints
|
||
|
||
### NEVER Do This
|
||
1. ❌ Change pair order (must follow PDF exactly)
|
||
2. ❌ Show feedback on correctness during test
|
||
3. ❌ Allow skipping trials
|
||
4. ❌ Let participants replay encoding phase
|
||
5. ❌ Accept category answers (require specific items)
|
||
6. ❌ Use emojis as actual stimuli (use real images)
|
||
7. ❌ Skip or abbreviate delay interval
|
||
8. ❌ Randomize recall cue order (follow PDF)
|
||
9. ❌ Show scores during test (only at end)
|
||
10. ❌ Use localStorage or sessionStorage
|
||
|
||
### ALWAYS Do This
|
||
1. ✅ Follow exact pair sequences from PDF
|
||
2. ✅ Use age-specific timing (3.5s vs 2.5s encoding)
|
||
3. ✅ Show "Time is up!" / "Too Late" on timeout
|
||
4. ✅ Validate exact item names only
|
||
5. ✅ Randomize recognition option positions
|
||
6. ✅ Track all metrics silently during test
|
||
7. ✅ Implement 15-minute delay with filler tasks
|
||
8. ✅ Use opposite cues for delayed vs immediate recall
|
||
9. ✅ Display clear, high-contrast images
|
||
10. ✅ Export complete data at end
|
||
|
||
---
|
||
|
||
## 🔧 Development Workflow
|
||
|
||
### Step-by-Step Process
|
||
1. **Current Phase:** Developer specifies which screen to create
|
||
2. **AI Creates:** Complete screen with all required files
|
||
3. **AI Shows:** Code for that screen only
|
||
4. **AI Asks:** "Screen [Name] complete. Proceed to [NextScreen]?"
|
||
5. **Developer:** Approves or requests changes
|
||
6. **Repeat:** Until all 11 screens are complete
|
||
7. **Integration:** Connect VPAM to TestSelectionScreen
|
||
8. **Testing:** Verify timing, data collection, export
|
||
|
||
### Code Quality Standards
|
||
- Use functional components with hooks
|
||
- Separate concerns (logic in hooks, UI in components)
|
||
- Add JSDoc comments for complex functions
|
||
- Follow consistent naming conventions (camelCase)
|
||
- Use Tailwind for ALL styling (no inline styles)
|
||
- Handle edge cases (timeout, rapid input, empty responses)
|
||
- Create reusable components (don't duplicate code)
|
||
- Optimize re-renders with useMemo/useCallback
|
||
- Implement proper cleanup in useEffect
|
||
|
||
### State Management Pattern
|
||
|
||
```javascript
|
||
// Main VPAM Test State (Context or prop drilling)
|
||
const [testState, setTestState] = useState({
|
||
currentScreen: 'intro', // 'intro', 'encoding', 'immediateRecall', etc.
|
||
ageGroup: null, // 'adolescent' or 'adult'
|
||
phase: null, // 'encoding', 'immediate_recall', 'delayed_recall', 'recognition'
|
||
currentTrial: 0, // 0-34 per phase
|
||
trialData: [], // All recorded trials
|
||
startTime: null,
|
||
delayStartTime: null,
|
||
delayEndTime: null
|
||
});
|
||
|
||
// Per-Screen Local State
|
||
const [screenState, setScreenState] = useState({
|
||
showStimulus: false,
|
||
currentPair: null,
|
||
userResponse: '',
|
||
isSubmitting: false,
|
||
showTimeout: false,
|
||
reactionStartTime: null
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 🎭 Animation Specifications
|
||
|
||
### Framer Motion Animations (utils/vpamAnimations.js)
|
||
|
||
```javascript
|
||
// Minimal, non-distracting animations
|
||
|
||
export const fadeIn = {
|
||
initial: { opacity: 0 },
|
||
animate: { opacity: 1 },
|
||
transition: { duration: 0.3 }
|
||
};
|
||
|
||
export const slideUp = {
|
||
initial: { y: 20, opacity: 0 },
|
||
animate: { y: 0, opacity: 1 },
|
||
transition: { duration: 0.4, ease: 'easeOut' }
|
||
};
|
||
|
||
export const pairTransition = {
|
||
initial: { opacity: 0, scale: 0.95 },
|
||
animate: { opacity: 1, scale: 1 },
|
||
exit: { opacity: 0, scale: 0.95 },
|
||
transition: { duration: 0.2 }
|
||
};
|
||
|
||
export const timeoutPulse = {
|
||
initial: { scale: 1 },
|
||
animate: {
|
||
scale: [1, 1.1, 1],
|
||
opacity: [1, 0.8, 0]
|
||
},
|
||
transition: { duration: 1 }
|
||
};
|
||
|
||
// NO complex animations during encoding/recall
|
||
// Keep cognitive load minimal
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 Timing Flow (CRITICAL)
|
||
|
||
### Encoding Phase Timing
|
||
|
||
```javascript
|
||
// For each of 35 pairs:
|
||
|
||
1. Display pair immediately (no fade-in delay)
|
||
- Both images visible simultaneously
|
||
- Centered in white box
|
||
|
||
2. Hold for exposure duration
|
||
- Adolescent: 3.5 seconds
|
||
- Adult: 2.5 seconds
|
||
- No user interaction
|
||
|
||
3. Brief blank screen (0.3 seconds)
|
||
- Inter-pair interval
|
||
- Prevents visual overlap
|
||
|
||
4. Next pair appears
|
||
- Repeat steps 1-3
|
||
|
||
5. After pair 35 → transition to Immediate Recall
|
||
- No delay, automatic progression
|
||
|
||
// Total encoding time:
|
||
// Adolescents: (3.5s + 0.3s) × 35 = 133 seconds (~2.2 min)
|
||
// Adults: (2.5s + 0.3s) × 35 = 98 seconds (~1.6 min)
|
||
```
|
||
|
||
### Recall Phase Timing
|
||
|
||
```javascript
|
||
// For each of 35 trials:
|
||
|
||
1. Display cue image immediately
|
||
- Start reaction timer
|
||
- Show text input field (empty)
|
||
|
||
2. Wait for response OR timeout (15 seconds)
|
||
- Listen for:
|
||
* Text input changes
|
||
* Submit button click
|
||
* Enter key press
|
||
* Timer expiration
|
||
|
||
3. On Submit (before timeout):
|
||
- Record response text
|
||
- Calculate reaction time
|
||
- Validate accuracy
|
||
- Clear input field
|
||
- Proceed to next trial immediately
|
||
|
||
4. On Timeout (no submit):
|
||
- Show "Time is up!" (1 second)
|
||
- Record as no response (accuracy = 0)
|
||
- Clear input field
|
||
- Proceed to next trial
|
||
|
||
5. Brief blank screen (optional, 0.3s)
|
||
- Or immediately show next cue
|
||
|
||
// Total recall time (worst case - all timeouts):
|
||
// (15s + 1s) × 35 = 560 seconds (~9.3 min per phase)
|
||
```
|
||
|
||
### Recognition Phase Timing
|
||
|
||
```javascript
|
||
// For each of 35 trials:
|
||
|
||
1. Display cue at top
|
||
2. Display 3 randomized options below
|
||
3. Start 4-second timer
|
||
4. Wait for selection OR timeout
|
||
|
||
5. On Selection (before timeout):
|
||
- Highlight selected option briefly (0.2s)
|
||
- Record choice and RT
|
||
- Validate accuracy
|
||
- Proceed to next trial immediately
|
||
|
||
6. On Timeout (no selection):
|
||
- Show "Too Late" (1 second)
|
||
- Record as no response (accuracy = 0)
|
||
- Proceed to next trial
|
||
|
||
// Total recognition time (worst case):
|
||
// (4s + 1s) × 35 = 175 seconds (~2.9 min)
|
||
```
|
||
|
||
### Delay Interval Timing
|
||
|
||
```javascript
|
||
// 15-minute delay with filler tasks
|
||
|
||
1. Show transition message (5 seconds)
|
||
2. Launch Response Inhibition Test (~7 min)
|
||
3. Launch Cognitive Flexibility Test (~6 min)
|
||
4. Launch Stroop Test (~2 min) [if available]
|
||
5. Auto-trigger Delayed Recall at 15:00
|
||
|
||
// Critical: Track elapsed time precisely
|
||
// Use Date.now() or performance.now()
|
||
// Don't rely on setTimeout for long durations
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 Scoring Logic (CRITICAL)
|
||
|
||
### Response Validation
|
||
|
||
```javascript
|
||
/**
|
||
* Validates user response against correct answer
|
||
* @param {string} userInput - Raw user input
|
||
* @param {string} correctAnswer - Expected answer
|
||
* @returns {boolean} - True if correct
|
||
*/
|
||
function validateResponse(userInput, correctAnswer) {
|
||
// Normalize both strings
|
||
const normalized = userInput
|
||
.toLowerCase()
|
||
.trim()
|
||
.replace(/[^a-z0-9\s]/g, ''); // Remove punctuation
|
||
|
||
const correct = correctAnswer
|
||
.toLowerCase()
|
||
.trim()
|
||
.replace(/[^a-z0-9\s]/g, '');
|
||
|
||
// Exact match required
|
||
if (normalized === correct) {
|
||
return true;
|
||
}
|
||
|
||
// Optional: Handle common variations
|
||
// "lady's finger" vs "ladys finger" vs "ladies finger"
|
||
const variations = generateVariations(correct);
|
||
return variations.includes(normalized);
|
||
}
|
||
|
||
/**
|
||
* Generate acceptable variations for hyphenated/possessive words
|
||
*/
|
||
function generateVariations(word) {
|
||
return [
|
||
word,
|
||
word.replace(/[\s-']/g, ''), // Remove spaces, hyphens, apostrophes
|
||
word.replace(/'/g, ''), // Remove apostrophes only
|
||
word.replace(/-/g, ' '), // Replace hyphens with spaces
|
||
word.replace(/\s/g, '') // Remove all spaces
|
||
];
|
||
}
|
||
```
|
||
|
||
### Accuracy Calculation
|
||
|
||
```javascript
|
||
/**
|
||
* Calculate accuracy metrics for each phase
|
||
*/
|
||
function calculateAccuracy(trialData, phase) {
|
||
const phaseTrials = trialData.filter(t => t.phase === phase);
|
||
const totalTrials = phaseTrials.length;
|
||
const answered = phaseTrials.filter(t => t.responseType !== 'timeout').length;
|
||
const correct = phaseTrials.filter(t => t.responseAccuracy === 1).length;
|
||
|
||
return {
|
||
totalTrials,
|
||
answered,
|
||
notAnswered: totalTrials - answered,
|
||
correct,
|
||
incorrect: answered - correct,
|
||
accuracyRate: totalTrials > 0 ? (correct / totalTrials) * 100 : 0,
|
||
answeredAccuracyRate: answered > 0 ? (correct / answered) * 100 : 0
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Calculate overall task metrics
|
||
*/
|
||
function calculateOverallMetrics(trialData) {
|
||
const immediateRecall = calculateAccuracy(trialData, 'immediate_recall');
|
||
const delayedRecall = calculateAccuracy(trialData, 'delayed_recall');
|
||
const recognition = calculateAccuracy(trialData, 'recognition');
|
||
|
||
// Consolidation slope
|
||
const absoluteChange = delayedRecall.accuracyRate - immediateRecall.accuracyRate;
|
||
const percentageChange = immediateRecall.accuracyRate > 0
|
||
? (absoluteChange / immediateRecall.accuracyRate) * 100
|
||
: 0;
|
||
|
||
// Overall accuracy
|
||
const totalCorrect = immediateRecall.correct + delayedRecall.correct + recognition.correct;
|
||
const totalTrials = immediateRecall.totalTrials + delayedRecall.totalTrials + recognition.totalTrials;
|
||
const overallAccuracy = totalTrials > 0 ? (totalCorrect / totalTrials) * 100 : 0;
|
||
|
||
return {
|
||
immediateRecall,
|
||
delayedRecall,
|
||
recognition,
|
||
consolidation: {
|
||
absoluteChange,
|
||
percentageChange,
|
||
forgettingRate: Math.max(0, -absoluteChange), // Only if negative
|
||
itemsRetained: Math.min(immediateRecall.correct, delayedRecall.correct)
|
||
},
|
||
overall: {
|
||
accuracy: overallAccuracy,
|
||
totalCorrect,
|
||
totalTrials,
|
||
totalAnswered: immediateRecall.answered + delayedRecall.answered + recognition.answered
|
||
}
|
||
};
|
||
}
|
||
```
|
||
|
||
### Reaction Time Calculation
|
||
|
||
```javascript
|
||
/**
|
||
* Calculate reaction time statistics
|
||
*/
|
||
function calculateReactionTimes(trialData, phase) {
|
||
const phaseTrials = trialData
|
||
.filter(t => t.phase === phase && t.responseTime !== null);
|
||
|
||
if (phaseTrials.length === 0) {
|
||
return { mean: null, median: null, sd: null, min: null, max: null };
|
||
}
|
||
|
||
const times = phaseTrials.map(t => t.responseTime);
|
||
const mean = times.reduce((a, b) => a + b, 0) / times.length;
|
||
|
||
// Standard deviation
|
||
const variance = times.reduce((sum, time) => {
|
||
return sum + Math.pow(time - mean, 2);
|
||
}, 0) / times.length;
|
||
const sd = Math.sqrt(variance);
|
||
|
||
// Median
|
||
const sorted = [...times].sort((a, b) => a - b);
|
||
const median = sorted.length % 2 === 0
|
||
? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
|
||
: sorted[Math.floor(sorted.length / 2)];
|
||
|
||
return {
|
||
mean: Math.round(mean),
|
||
median: Math.round(median),
|
||
sd: Math.round(sd),
|
||
min: Math.min(...times),
|
||
max: Math.max(...times),
|
||
count: times.length
|
||
};
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 Component Examples
|
||
|
||
### PairDisplay Component
|
||
|
||
```javascript
|
||
// components/shared/VPAM/PairDisplay.jsx
|
||
|
||
import React from 'react';
|
||
import { motion } from 'framer-motion';
|
||
|
||
/**
|
||
* Displays a pair of images during encoding phase
|
||
*/
|
||
export default function PairDisplay({ firstImage, secondImage, ageGroup }) {
|
||
const containerBg = ageGroup === 'adolescent'
|
||
? 'bg-gradient-to-br from-purple-100 to-blue-100'
|
||
: 'bg-gray-50';
|
||
|
||
return (
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.95 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
exit={{ opacity: 0, scale: 0.95 }}
|
||
transition={{ duration: 0.2 }}
|
||
className={`${containerBg} min-h-screen flex items-center justify-center p-4`}
|
||
>
|
||
<div className="bg-white border-4 border-gray-300 rounded-2xl p-8 shadow-lg">
|
||
<div className="flex flex-col md:flex-row items-center justify-center gap-8 md:gap-12">
|
||
<img
|
||
src={firstImage}
|
||
alt="First item"
|
||
className="w-40 h-40 md:w-48 md:h-48 object-contain"
|
||
/>
|
||
<div className="text-4xl text-gray-400 font-bold">+</div>
|
||
<img
|
||
src={secondImage}
|
||
alt="Second item"
|
||
className="w-40 h-40 md:w-48 md:h-48 object-contain"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### RecallInput Component
|
||
|
||
```javascript
|
||
// components/shared/VPAM/RecallInput.jsx
|
||
|
||
import React, { useState, useEffect, useRef } from 'react';
|
||
import { motion } from 'framer-motion';
|
||
|
||
/**
|
||
* Input component for recall phases
|
||
*/
|
||
export default function RecallInput({
|
||
cueImage,
|
||
onSubmit,
|
||
timeLimit = 15000,
|
||
showTimeout,
|
||
disabled
|
||
}) {
|
||
const [input, setInput] = useState('');
|
||
const [timeRemaining, setTimeRemaining] = useState(timeLimit / 1000);
|
||
const inputRef = useRef(null);
|
||
|
||
useEffect(() => {
|
||
// Focus input on mount
|
||
inputRef.current?.focus();
|
||
}, [cueImage]);
|
||
|
||
useEffect(() => {
|
||
// Countdown timer
|
||
const interval = setInterval(() => {
|
||
setTimeRemaining(prev => Math.max(0, prev - 0.1));
|
||
}, 100);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
const handleSubmit = (e) => {
|
||
e.preventDefault();
|
||
if (input.trim() && !disabled) {
|
||
onSubmit(input.trim());
|
||
setInput('');
|
||
}
|
||
};
|
||
|
||
const progressPercent = (timeRemaining / (timeLimit / 1000)) * 100;
|
||
const isUrgent = timeRemaining < 5;
|
||
|
||
return (
|
||
<div className="flex flex-col items-center gap-6">
|
||
{/* Cue Image */}
|
||
<div className="bg-white border-4 border-gray-300 rounded-2xl p-6 shadow-lg">
|
||
<img
|
||
src={cueImage}
|
||
alt="Cue"
|
||
className="w-48 h-48 object-contain"
|
||
/>
|
||
</div>
|
||
|
||
{/* Timer Bar */}
|
||
<div className="w-full max-w-md h-2 bg-gray-200 rounded-full overflow-hidden">
|
||
<motion.div
|
||
className={`h-full ${isUrgent ? 'bg-red-500' : 'bg-blue-500'}`}
|
||
initial={{ width: '100%' }}
|
||
animate={{ width: `${progressPercent}%` }}
|
||
transition={{ duration: 0.1 }}
|
||
/>
|
||
</div>
|
||
|
||
{/* Input Form */}
|
||
<form onSubmit={handleSubmit} className="w-full max-w-md">
|
||
<input
|
||
ref={inputRef}
|
||
type="text"
|
||
value={input}
|
||
onChange={(e) => setInput(e.target.value)}
|
||
disabled={disabled}
|
||
placeholder="Type the paired item..."
|
||
maxLength={50}
|
||
className="w-full px-6 py-4 text-lg border-3 border-gray-400
|
||
rounded-xl focus:border-blue-500 focus:ring-4
|
||
focus:ring-blue-200 outline-none transition-all
|
||
disabled:bg-gray-100 disabled:cursor-not-allowed"
|
||
/>
|
||
|
||
<button
|
||
type="submit"
|
||
disabled={!input.trim() || disabled}
|
||
className="w-full mt-4 px-6 py-4 text-lg font-semibold
|
||
bg-blue-500 text-white rounded-xl
|
||
hover:bg-blue-600 active:scale-95
|
||
disabled:bg-gray-300 disabled:cursor-not-allowed
|
||
transition-all"
|
||
>
|
||
Submit Answer
|
||
</button>
|
||
</form>
|
||
|
||
{/* Timeout Message */}
|
||
{showTimeout && (
|
||
<motion.div
|
||
initial={{ scale: 0.8, opacity: 0 }}
|
||
animate={{ scale: 1, opacity: 1 }}
|
||
exit={{ scale: 0.8, opacity: 0 }}
|
||
className="fixed inset-0 flex items-center justify-center
|
||
bg-black bg-opacity-50 z-50"
|
||
>
|
||
<div className="bg-white px-12 py-8 rounded-2xl shadow-2xl">
|
||
<p className="text-3xl font-bold text-red-600">Time is up!</p>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### RecognitionOptions Component
|
||
|
||
```javascript
|
||
// components/shared/VPAM/RecognitionOptions.jsx
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import { motion } from 'framer-motion';
|
||
|
||
/**
|
||
* Three-option selection for recognition phase
|
||
*/
|
||
export default function RecognitionOptions({
|
||
cueImage,
|
||
options, // [{ id, image, isCorrect }, ...]
|
||
onSelect,
|
||
timeLimit = 4000,
|
||
showTimeout,
|
||
disabled
|
||
}) {
|
||
const [timeRemaining, setTimeRemaining] = useState(timeLimit / 1000);
|
||
const [selectedId, setSelectedId] = useState(null);
|
||
|
||
useEffect(() => {
|
||
const interval = setInterval(() => {
|
||
setTimeRemaining(prev => Math.max(0, prev - 0.1));
|
||
}, 100);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
const handleSelect = (option) => {
|
||
if (disabled || selectedId) return;
|
||
|
||
setSelectedId(option.id);
|
||
setTimeout(() => {
|
||
onSelect(option);
|
||
}, 200); // Brief highlight before advancing
|
||
};
|
||
|
||
const progressPercent = (timeRemaining / (timeLimit / 1000)) * 100;
|
||
const isUrgent = timeRemaining < 2;
|
||
|
||
return (
|
||
<div className="flex flex-col items-center gap-8">
|
||
{/* Cue Image */}
|
||
<div className="bg-white border-4 border-gray-300 rounded-2xl p-6 shadow-lg">
|
||
<img
|
||
src={cueImage}
|
||
alt="Cue"
|
||
className="w-32 h-32 object-contain"
|
||
/>
|
||
</div>
|
||
|
||
{/* Timer Bar */}
|
||
<div className="w-full max-w-2xl h-3 bg-gray-200 rounded-full overflow-hidden">
|
||
<motion.div
|
||
className={`h-full ${isUrgent ? 'bg-red-500' : 'bg-green-500'}`}
|
||
initial={{ width: '100%' }}
|
||
animate={{ width: `${progressPercent}%` }}
|
||
transition={{ duration: 0.1 }}
|
||
/>
|
||
</div>
|
||
|
||
{/* Options */}
|
||
<div className="flex flex-wrap justify-center gap-6">
|
||
{options.map((option) => (
|
||
<motion.button
|
||
key={option.id}
|
||
onClick={() => handleSelect(option)}
|
||
disabled={disabled}
|
||
whileHover={{ scale: disabled ? 1 : 1.05 }}
|
||
whileTap={{ scale: disabled ? 1 : 0.95 }}
|
||
className={`
|
||
border-4 rounded-2xl p-4 transition-all
|
||
${selectedId === option.id
|
||
? 'border-blue-500 bg-blue-50 ring-4 ring-blue-200'
|
||
: 'border-gray-300 bg-white hover:border-blue-400'
|
||
}
|
||
${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}
|
||
`}
|
||
>
|
||
<img
|
||
src={option.image}
|
||
alt="Option"
|
||
className="w-32 h-32 object-contain"
|
||
/>
|
||
</motion.button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Timeout Message */}
|
||
{showTimeout && (
|
||
<motion.div
|
||
initial={{ scale: 0.8, opacity: 0 }}
|
||
animate={{ scale: 1, opacity: 1 }}
|
||
exit={{ scale: 0.8, opacity: 0 }}
|
||
className="fixed inset-0 flex items-center justify-center
|
||
bg-black bg-opacity-50 z-50"
|
||
>
|
||
<div className="bg-white px-12 py-8 rounded-2xl shadow-2xl">
|
||
<p className="text-3xl font-bold text-orange-600">Too Late!</p>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 Data Export Implementation
|
||
|
||
### vpamDataExport.js
|
||
|
||
```javascript
|
||
/**
|
||
* Export VPAM test results in multiple formats
|
||
*/
|
||
|
||
/**
|
||
* Export as JSON file
|
||
*/
|
||
export function exportJSON(data, ageGroup) {
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||
const filename = `vpam_results_${ageGroup}_${timestamp}.json`;
|
||
|
||
const jsonStr = JSON.stringify(data, null, 2);
|
||
const blob = new Blob([jsonStr], { type: 'application/json' });
|
||
downloadBlob(blob, filename);
|
||
}
|
||
|
||
/**
|
||
* Export as CSV file
|
||
*/
|
||
export function exportCSV(data, ageGroup) {
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||
const filename = `vpam_results_${ageGroup}_${timestamp}.csv`;
|
||
|
||
// CSV Headers
|
||
const headers = [
|
||
'Trial_Number',
|
||
'Phase',
|
||
'Trial_In_Phase',
|
||
'Cue_Item',
|
||
'Target_Item',
|
||
'Participant_Response',
|
||
'Response_Time_ms',
|
||
'Accuracy',
|
||
'Response_Type',
|
||
'Selected_Option',
|
||
'Timestamp'
|
||
];
|
||
|
||
// Convert trial data to CSV rows
|
||
const rows = data.trialData.map(trial => [
|
||
trial.trialNumber,
|
||
trial.phase,
|
||
trial.trialInPhase,
|
||
trial.cueItem || 'N/A',
|
||
trial.targetItem,
|
||
trial.participantResponse || '',
|
||
trial.responseTime || '',
|
||
trial.responseAccuracy,
|
||
trial.responseType,
|
||
trial.selectedOption || 'N/A',
|
||
trial.timestamp
|
||
]);
|
||
|
||
// Combine headers and rows
|
||
const csvContent = [
|
||
headers.join(','),
|
||
...rows.map(row => row.map(escapeCSV).join(','))
|
||
].join('\n');
|
||
|
||
const blob = new Blob([csvContent], { type: 'text/csv' });
|
||
downloadBlob(blob, filename);
|
||
}
|
||
|
||
/**
|
||
* Escape CSV values
|
||
*/
|
||
function escapeCSV(value) {
|
||
if (value === null || value === undefined) return '';
|
||
const str = String(value);
|
||
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
||
return `"${str.replace(/"/g, '""')}"`;
|
||
}
|
||
return str;
|
||
}
|
||
|
||
/**
|
||
* Trigger browser download
|
||
*/
|
||
function downloadBlob(blob, filename) {
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = filename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
/**
|
||
* Export summary report (human-readable)
|
||
*/
|
||
export function exportSummaryReport(data, ageGroup) {
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||
const filename = `vpam_summary_${ageGroup}_${timestamp}.txt`;
|
||
|
||
const report = `
|
||
VISUAL PAIRED ASSOCIATES MEMORY TEST - SUMMARY REPORT
|
||
====================================================
|
||
|
||
Test Information:
|
||
-----------------
|
||
Age Group: ${ageGroup === 'adolescent' ? 'Adolescent (14-18)' : 'Adult (18-22)'}
|
||
Test Date: ${data.participantInfo.testDate}
|
||
Total Duration: ${data.participantInfo.totalDuration} minutes
|
||
Delay Duration: ${data.participantInfo.delayDurationActual} minutes
|
||
|
||
Overall Performance:
|
||
--------------------
|
||
Total Trials: ${data.responseCounts.totalTrials}
|
||
Total Responses: ${data.responseCounts.totalResponses}
|
||
Correct Responses: ${data.responseCounts.correctResponses}
|
||
Overall Accuracy: ${data.accuracy.overall.toFixed(2)}%
|
||
|
||
Phase-Specific Accuracy:
|
||
------------------------
|
||
Immediate Recall: ${data.accuracy.immediateRecall.toFixed(2)}%
|
||
Delayed Recall: ${data.accuracy.delayedRecall.toFixed(2)}%
|
||
Recognition: ${data.accuracy.recognition.toFixed(2)}%
|
||
|
||
Reaction Times (Mean):
|
||
----------------------
|
||
Immediate Recall: ${data.reactionTimes.immediateRecallMean}ms
|
||
Delayed Recall: ${data.reactionTimes.delayedRecallMean}ms
|
||
Recognition: ${data.reactionTimes.recognitionMean}ms
|
||
|
||
Memory Consolidation:
|
||
---------------------
|
||
Absolute Change: ${data.consolidation.absoluteChange.toFixed(2)}%
|
||
Percentage Change: ${data.consolidation.percentageChange.toFixed(2)}%
|
||
Items Retained: ${data.consolidation.itemsRetained} / 35
|
||
|
||
====================================================
|
||
`.trim();
|
||
|
||
const blob = new Blob([report], { type: 'text/plain' });
|
||
downloadBlob(blob, filename);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 Testing Checklist
|
||
|
||
### Before Declaring Complete
|
||
|
||
- [ ] **All 11 screens functional** and transition properly
|
||
- [ ] **Timing accuracy** verified with stopwatch
|
||
- [ ] Encoding: 3.5s (adolescent) / 2.5s (adult) per pair
|
||
- [ ] Recall: 15s response window
|
||
- [ ] Recognition: 4s response window
|
||
- [ ] Delay: Exactly 15 minutes
|
||
- [ ] **Image loading** works for all 210+ images
|
||
- [ ] **Response validation** correctly identifies exact matches
|
||
- [ ] **Data recording** captures all required fields
|
||
- [ ] **Timeout messages** display correctly
|
||
- [ ] **Recognition randomization** works (options in different positions)
|
||
- [ ] **Calculations correct** for all metrics
|
||
- [ ] **Export functions** produce valid JSON/CSV files
|
||
- [ ] **Age-specific content** displays properly for both groups
|
||
- [ ] **Responsive design** works on mobile (345px) to desktop
|
||
- [ ] **No console errors** or warnings
|
||
- [ ] **Memory leaks** prevented (proper cleanup in useEffect)
|
||
- [ ] **Keyboard navigation** works (Enter to submit, Tab through options)
|
||
- [ ] **Accessibility** basics (alt text, ARIA labels, focus indicators)
|
||
|
||
---
|
||
|
||
## 🎓 Integration with Existing Project
|
||
|
||
### Update TestSelectionScreen
|
||
|
||
```javascript
|
||
// components/screens/TestSelectionScreen.jsx
|
||
|
||
const tests = [
|
||
{
|
||
id: 'rit',
|
||
name: 'Response Inhibition Task',
|
||
description: 'Go/No-Go test measuring impulse control',
|
||
component: ResponseInhibitionTest,
|
||
estimatedTime: '8-10 minutes'
|
||
},
|
||
{
|
||
id: 'cft',
|
||
name: 'Cognitive Flexibility Test',
|
||
description: 'Tests ability to switch between rules',
|
||
component: CognitiveFlexibilityTest,
|
||
estimatedTime: '10-12 minutes'
|
||
},
|
||
{
|
||
id: 'vpam', // NEW
|
||
name: 'Visual Paired Associates Memory',
|
||
description: 'Tests associative memory and retention',
|
||
component: VisualPairedAssociatesTest,
|
||
estimatedTime: '30-35 minutes'
|
||
}
|
||
];
|
||
```
|
||
|
||
### Update testsConfig.js
|
||
|
||
```javascript
|
||
// constants/testsConfig.js
|
||
|
||
export const TESTS_CONFIG = {
|
||
rit: {
|
||
name: 'Response Inhibition Task',
|
||
shortName: 'RIT',
|
||
route: '/rit',
|
||
color: 'blue'
|
||
},
|
||
cft: {
|
||
name: 'Cognitive Flexibility Test',
|
||
shortName: 'CFT',
|
||
route: '/cft',
|
||
color: 'purple'
|
||
},
|
||
vpam: { // NEW
|
||
name: 'Visual Paired Associates Memory',
|
||
shortName: 'VPAM',
|
||
route: '/vpam',
|
||
color: 'green'
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Final Notes
|
||
|
||
### Research Validity Reminders
|
||
- This is a **standardized memory test** - ANY deviation from specifications invalidates research data
|
||
- Timing must be **precise to the millisecond** where possible
|
||
- Trial order is **predetermined, not randomized** - follow PDF exactly
|
||
- Response validation must be **strict** (exact item names only)
|
||
- Data export must include **ALL metrics** for statistical analysis
|
||
|
||
### Performance Optimization
|
||
- Preload ALL images before starting encoding phase
|
||
- Use `React.memo()` for heavy components
|
||
- Debounce text input validation (but record raw input)
|
||
- Clear timers and intervals properly to prevent memory leaks
|
||
- Consider using Web Workers for data calculations if needed
|
||
|
||
### Accessibility Considerations
|
||
- Keyboard navigation for all inputs
|
||
- Screen reader compatibility (ARIA labels)
|
||
- High contrast mode support
|
||
- Focus indicators clearly visible
|
||
- Text size adjustable (relative units)
|
||
|
||
### Future Enhancements (Optional)
|
||
- Practice trials for recall/recognition phases
|
||
- Audio cues (optional mode)
|
||
- Multi-language support
|
||
- Adaptive difficulty (not standard, research variant)
|
||
- Real-time data sync to server
|
||
- Progress save/resume functionality
|
||
|
||
---
|
||
|
||
## ✅ Development Completion Criteria
|
||
|
||
The VPAM test is considered complete when:
|
||
|
||
1. ✅ All 11 screens implemented and functional
|
||
2. ✅ All timing specifications met precisely
|
||
3. ✅ All 210+ images loaded and displayed correctly
|
||
4. ✅ Data recording captures all required metrics
|
||
5. ✅ Export functions produce valid, complete data files
|
||
6. ✅ Age-specific content displays correctly
|
||
7. ✅ Responsive design works across all screen sizes
|
||
8. ✅ No bugs, errors, or console warnings
|
||
9. ✅ Code is clean, commented, and maintainable
|
||
10. ✅ Integration with existing project seamless
|
||
11. ✅ Testing checklist 100% verified
|
||
12. ✅ Documentation updated
|
||
|
||
---
|
||
|
||
**IMPORTANT REMINDERS:**
|
||
- **ONE SCREEN AT A TIME** - wait for:**
|
||
- `components/screens/VPAM/IntroScreen.jsx`
|
||
- `constants/vpamInstructions.js`
|
||
|
||
**Content:**
|
||
```javascript
|
||
// Generic welcome message (shown to all)
|
||
"In this game, you will see pictures of everyday things. Some of these
|
||
pictures will be shown together as pairs. Your task is to pay close
|
||
attention and try to remember which ones are being presented as pairs,
|
||
because later we will test your memory in different ways. Sometimes you
|
||
will have to write the missing item, and sometimes you will choose it
|
||
from a few options. Let's get started!"
|
||
```
|
||
|
||
**State to initialize:**
|
||
- `ageGroup` (null → 'adolescent' or 'adult')
|
||
- `currentScreen` ('intro')
|
||
|
||
---
|
||
|
||
#### Screen 2: EncodingInstructionScreen
|
||
**Purpose:** Explain the learning phase before showing pairs
|
||
|
||
**Components needed:**
|
||
- Instruction text display
|
||
- Example pair visualization
|
||
- Duration explanation
|
||
- "Let's Go!" button
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/EncodingInstructionScreen.jsx`
|
||
|
||
**Instructions:**
|
||
|
||
Adolescents:
|
||
```
|
||
"You will see two pictures appear inside a box on the screen. Just look
|
||
carefully and try to remember which pictures are being presented as pairs.
|
||
You don't need to press anything. Each pair will disappear after a few
|
||
seconds, so focus while it is on the screen."
|
||
```
|
||
|
||
Adults:
|
||
```
|
||
"You will see two items presented together inside a box. Your task is to
|
||
observe carefully and remember which items are being presented as pairs.
|
||
You don't need to press anything. Each pair will only remain on screen
|
||
briefly, so focus while it is shown."
|
||
```
|
||
|
||
---
|
||
|
||
#### Screen 3: EncodingScreen
|
||
**Purpose:** Display all 35 pairs for learning
|
||
|
||
**Components needed:**
|
||
- White box container (centered)
|
||
- Two image displays (side by side)
|
||
- Automatic timer
|
||
- Progress indicator (optional: "Pair 12 of 35")
|
||
- Auto-advance logic
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/EncodingScreen.jsx`
|
||
- `components/shared/VPAM/PairDisplay.jsx`
|
||
- `hooks/vpam/useEncodingScreen.js`
|
||
- `utils/vpamTimer.js`
|
||
|
||
**Critical logic:**
|
||
```javascript
|
||
// For each pair in sequence:
|
||
1. Display both images together
|
||
2. Start timer (3.5s adolescent / 2.5s adult)
|
||
3. Wait for timer completion (NO user input)
|
||
4. Clear screen briefly
|
||
5. Load next pair
|
||
6. After pair 35 → transition to Immediate Recall
|
||
```
|
||
|
||
**Timing precision:**
|
||
- Use `setTimeout` or `setInterval`
|
||
- Track exposure time per pair
|
||
- Record timestamp for each pair shown
|
||
|
||
---
|
||
|
||
#### Screen 4: ImmediateRecallInstructionScreen
|
||
**Purpose:** Explain the typing recall task
|
||
|
||
**Components needed:**
|
||
- Instruction text display
|
||
- Example with visual
|
||
- Response format explanation
|
||
- "Let's Go!" button
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/ImmediateRecallInstructionScreen.jsx`
|
||
|
||
**Instructions:**
|
||
|
||
Adolescents:
|
||
```
|
||
"Great job learning the pairs! Now, one item from each pair will appear
|
||
by itself. Your task is to write the item that goes with it. Try to answer
|
||
quickly — you'll have a short time for each one.
|
||
|
||
Remember: write the exact item that was paired. Don't just write a general
|
||
category, color, or size — focus on the specific item.
|
||
|
||
Example: If you see 🍎 Apple and it was paired with 🪑 Chair, write 'Chair',
|
||
not 'furniture' or 'brown chair' or 'big chair'."
|
||
```
|
||
|
||
Adults:
|
||
```
|
||
"You've just seen all the pairs. Now, a single item from each pair will
|
||
appear alone. Your task is to type or write the item that was originally
|
||
paired with it. Respond quickly — each trial has a time limit.
|
||
|
||
Type the exact item that was paired. Do not write a general category, color,
|
||
or size — focus on the specific item.
|
||
|
||
Example: If the cue is 🖊️ Pen and it was paired with 🐘 Elephant, type
|
||
'Elephant', not 'animal', 'creature', or 'grey elephant'."
|
||
```
|
||
|
||
---
|
||
|
||
#### Screen 5: ImmediateRecallScreen
|
||
**Purpose:** Test immediate memory with typed responses
|
||
|
||
**Components needed:**
|
||
- Cue image display (white box, centered)
|
||
- Text input field (below cue)
|
||
- Submit button
|
||
- Timer countdown (optional visual)
|
||
- "Time is up!" message overlay
|
||
- Trial counter
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/ImmediateRecallScreen.jsx`
|
||
- `components/shared/VPAM/RecallInput.jsx`
|
||
- `hooks/vpam/useRecallScreen.js`
|
||
|
||
**Critical logic:**
|
||
```javascript
|
||
// For each trial:
|
||
1. Display cue image
|
||
2. Start 15-second timer
|
||
3. Show text input field
|
||
4. Listen for:
|
||
- Submit button click
|
||
- Enter key press
|
||
- Timer expiration
|
||
5. Validate response against correct answer
|
||
6. Record:
|
||
- Response text
|
||
- Reaction time (ms from cue to submit)
|
||
- Accuracy (1/0)
|
||
7. If timeout → show "Time is up!" for 1s
|
||
8. Clear input and load next trial
|
||
9. After trial 35 → transition to Delay Interval
|
||
```
|
||
|
||
**Response validation:**
|
||
```javascript
|
||
function validateResponse(userInput, correctAnswer) {
|
||
// Normalize both strings
|
||
const normalized = userInput.toLowerCase().trim();
|
||
const correct = correctAnswer.toLowerCase().trim();
|
||
|
||
// Exact match required
|
||
return normalized === correct;
|
||
|
||
// Optional: Allow minor typos (Levenshtein distance ≤ 1)
|
||
// But be careful - too lenient affects data validity
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### Screen 6: DelayIntervalScreen
|
||
**Purpose:** Transition message and launch filler tasks
|
||
|
||
**Components needed:**
|
||
- Transition message display
|
||
- Automatic task chaining
|
||
- Timer tracking (15 minutes total)
|
||
- Progress indicator (optional)
|
||
|
||
**Required files:**
|
||
- `components/screens/VPAM/DelayIntervalScreen.jsx`
|
||
- `hooks/vpam/useDelayInterval.js`
|
||
|
||
**Transition messages:**
|
||
|
||
Adolescents:
|
||
```
|
||
"Great job looking at all the pairs! Before we test your memory, you'll
|
||
play a few short brain-teaser games. These games are like a quick workout
|
||
for your mind. Once you're done, we'll bring you back to check how many
|
||
pairs you can remember!"
|
||
```
|
||
|
||
Adults:
|
||
```
|
||
"Well done—you've completed the first part. Next, you'll do a few short
|
||
thinking activities designed to keep your mind active while your memory
|
||
continues to work in the background. After that, we'll return to test how
|
||
well you remember the pairs."
|
||
```
|
||
|
||
**Filler task sequence:**
|
||
```javascript
|
||
// Import and chain existing components:
|
||
1. <ResponseInhibitionTest /> (Go/No-Go)
|
||
2. <CognitiveFlexibilityTest /> (CFT)
|
||
3. <StroopTest /> (Attention - if available)
|
||
|
||
// Track elapsed time
|
||
// At 15:00 → auto-launch Delayed Recall
|
||
```
|
||
|
||
---
|
||
|
||
#### Screen 7: DelayedRecallInstructionScreen
|
||
**Purpose:** Re-explain recall task after delay
|
||
|
||
**Components needed:**
|
||
- "Welcome back" message
|
||
- Reminder of task rules
|
||
- "Let's Go!" button
|
||
|
||
**Required files |