DEV Community

Farzana Ali
Farzana Ali

Posted on

MindCheck (Dementia Warning)

DEV Weekend Challenge: Community

This is a submission for the DEV Weekend Challenge: Community

The Community

<MindCheck was built for elderly citizens—specifically those in their late 50s and older—who may be experiencing the early, often subtle, onset of cognitive decline or dementia.

In many communities, memory loss is dismissed as "just getting old." However, early detection of conditions like Alzheimer’s is life-changing. Many seniors live in areas where specialized neurological care is expensive or far away. They need a simple, non-intimidating way to monitor their own brain health at home. MindCheck bridges this gap, providing a daily "cognitive check-in" that empowers seniors and their families to notice changes early enough to seek professional medical help.>

What I Built

<MindCheck is an Early Dementia Warning App that turns clinical cognitive screening into a simple, engaging daily habit. It focuses on the three primary "red flag" areas of early dementia: Memory, Pattern Recognition, and Orientation.

Core Features:

Daily Cognitive Score: A weighted 1-100 score that tracks daily performance across all tests, providing instant feedback and long-term trends.

Word Memory Test: A study-and-recall challenge that tests short-term verbal memory, one of the first areas affected by Alzheimer’s.

Pattern Recognition: A visual sequence game designed to test the brain's "working memory" and spatial processing.

Orientation Quiz: Daily questions about time, date, and place to ensure the user remains grounded in their environment.

Symptom Tracker: A checklist of behavioral and physical symptoms that helps users document changes for their next doctor's visit.

Risk Meter: A visual data tool that analyzes the last 7 days of performance to categorize the user’s current cognitive risk level (Low, Moderate, or High).

The app uses high-contrast design, large fonts (Fraunces and DM Sans), and simple navigation to ensure it is accessible to those with declining eyesight or limited tech experience.>

Demo

https://gist.github.com/farzanaalitania/8856427b0ca7104cc1fd848c6feeb734

Code

<!<!DOCTYPE html>




MindCheck — Early Dementia Warning App
:root { --navy: #1A2744; --navy2: #243460; --blue: #3D5FC1; --blue-soft: #EEF2FC; --gold: #E8A838; --gold-soft: #FEF7E8; --green: #2DA068; --green-soft: #E6F7EF; --red: #D04040; --red-soft: #FDEEEE; --orange: #E07030; --orange-soft: #FEF3EC; --purple: #7B5EA7; --purple-soft: #F2EDFB; --bg: #F5F7FC; --white: #FFFFFF; --ink: #1A2744; --muted: #6B7FA8; --line: #DDE3F0; --shadow: 0 2px 20px rgba(26,39,68,0.09); --shadow-lg: 0 8px 40px rgba(26,39,68,0.15); } *,*::before,*::after{margin:0;padding:0;box-sizing:border-box;} html{scroll-behavior:smooth;} body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--ink);min-height:100vh;font-size:15px;} /* HEADER */ .hdr{background:linear-gradient(135deg,var(--navy) 0%,var(--navy2) 100%);padding:20px 20px 0;position:sticky;top:0;z-index:200;box-shadow:0 2px 24px rgba(26,39,68,0.3);} .hdr-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;} .brand{display:flex;align-items:center;gap:10px;} .brand-icon{width:42px;height:42px;background:rgba(255,255,255,0.12);border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:22px;} .brand-name{color:#fff;font-family:'Fraunces',serif;font-size:21px;font-weight:600;} .brand-name small{display:block;font-family:'DM Sans',sans-serif;font-size:11px;opacity:.6;font-weight:400;letter-spacing:.3px;margin-top:1px;} .streak-badge{background:rgba(232,168,56,0.2);border:1px solid rgba(232,168,56,0.4);border-radius:20px;padding:6px 14px;display:flex;align-items:center;gap:6px;color:var(--gold);font-size:13px;font-weight:700;} /* TABS */ .tabs{display:flex;overflow-x:auto;scrollbar-width:none;} .tabs::-webkit-scrollbar{display:none;} .tab{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:3px;padding:10px 16px 0;border:none;background:transparent;cursor:pointer;color:rgba(255,255,255,0.5);font-family:'DM Sans',sans-serif;font-size:10px;font-weight:700;border-bottom:3px solid transparent;transition:all .2s;white-space:nowrap;text-transform:uppercase;letter-spacing:.5px;} .tab .ti{font-size:19px;} .tab.on{color:#fff;border-bottom-color:var(--gold);} /* PAGES */ .page{display:none;padding:20px 16px 100px;animation:fadeUp .25s ease;} .page.on{display:block;} @keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}} /* CARDS */ .card{background:var(--white);border-radius:18px;padding:20px;box-shadow:var(--shadow);margin-bottom:14px;border:1.5px solid var(--line);} .card-blue{border-top:4px solid var(--blue);} .card-gold{border-top:4px solid var(--gold);} .card-green{border-top:4px solid var(--green);} .card-red{border-top:4px solid var(--red);} .card-purple{border-top:4px solid var(--purple);} /* SECTION HEADER */ .sh{margin-bottom:18px;} .sh-title{font-family:'Fraunces',serif;font-size:22px;font-weight:600;color:var(--ink);} .sh-sub{font-size:13px;color:var(--muted);margin-top:3px;} /* SCORE RING on HOME */ .score-hero{background:linear-gradient(135deg,var(--navy),var(--navy2));border-radius:20px;padding:28px 24px;margin-bottom:16px;text-align:center;position:relative;overflow:hidden;} .score-hero::before{content:'';position:absolute;top:-40px;right:-40px;width:180px;height:180px;border-radius:50%;background:rgba(255,255,255,0.04);} .score-hero::after{content:'';position:absolute;bottom:-60px;left:-30px;width:220px;height:220px;border-radius:50%;background:rgba(61,95,193,0.15);} .score-ring-wrap{position:relative;width:130px;height:130px;margin:0 auto 16px;} .score-svg{width:130px;height:130px;transform:rotate(-90deg);} .score-svg circle{fill:none;stroke-width:10;stroke-linecap:round;} .score-bg-circle{stroke:rgba(255,255,255,0.1);} .score-fg-circle{stroke:var(--gold);transition:stroke-dashoffset 1s ease;stroke-dasharray:339.3;stroke-dashoffset:339.3;} .score-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;} .score-num{font-family:'Fraunces',serif;font-size:36px;font-weight:900;color:#fff;line-height:1;} .score-label{font-size:11px;color:rgba(255,255,255,0.6);margin-top:2px;text-transform:uppercase;letter-spacing:.5px;} .score-title{color:#fff;font-family:'Fraunces',serif;font-size:18px;font-weight:600;margin-bottom:6px;position:relative;z-index:1;} .score-desc{color:rgba(255,255,255,0.7);font-size:13px;position:relative;z-index:1;line-height:1.6;} /* CHIPS */ .chip{display:inline-block;padding:4px 12px;border-radius:20px;font-size:11px;font-weight:700;} .cg{background:var(--green-soft);color:var(--green);} .cy{background:var(--gold-soft);color:var(--gold);} .cr{background:var(--red-soft);color:var(--red);} .cb{background:var(--blue-soft);color:var(--blue);} .cp{background:var(--purple-soft);color:var(--purple);} .co{background:var(--orange-soft);color:var(--orange);} /* NOTICES */ .notice{border-radius:13px;padding:13px 16px;font-size:13px;line-height:1.7;margin-bottom:14px;border-left:4px solid;} .ni{background:var(--blue-soft);border-color:var(--blue);color:#1a3080;} .nw{background:var(--gold-soft);border-color:var(--gold);color:#7a5000;} .nd{background:var(--red-soft);border-color:var(--red);color:#8a1010;} .ng{background:var(--green-soft);border-color:var(--green);color:#0a5030;} /* GAME AREA */ .game-box{background:var(--white);border-radius:20px;padding:24px;box-shadow:var(--shadow-lg);margin-bottom:14px;border:2px solid var(--line);min-height:200px;} .game-title{font-family:'Fraunces',serif;font-size:18px;font-weight:600;margin-bottom:6px;} .game-sub{font-size:13px;color:var(--muted);margin-bottom:20px;} .game-display{background:var(--bg);border-radius:14px;padding:20px;text-align:center;margin-bottom:16px;min-height:80px;display:flex;align-items:center;justify-content:center;flex-direction:column;} .game-big{font-family:'Fraunces',serif;font-size:48px;font-weight:900;color:var(--navy);letter-spacing:8px;} .game-word{font-family:'Fraunces',serif;font-size:32px;font-weight:600;color:var(--navy);letter-spacing:2px;} /* ANSWER GRID */ .ans-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px;} .ans-btn{padding:14px 10px;border-radius:14px;border:2px solid var(--line);background:var(--white);cursor:pointer;font-family:'DM Sans',sans-serif;font-size:15px;font-weight:700;text-align:center;transition:all .2s;box-shadow:var(--shadow);} .ans-btn:hover{border-color:var(--blue);background:var(--blue-soft);transform:translateY(-2px);} .ans-btn.correct{border-color:var(--green);background:var(--green-soft);color:var(--green);} .ans-btn.wrong{border-color:var(--red);background:var(--red-soft);color:var(--red);} .ans-btn:disabled{cursor:not-allowed;opacity:.7;} /* INPUT ANSWER */ .ans-input{width:100%;padding:14px 16px;border-radius:12px;border:2px solid var(--line);font-size:20px;font-weight:700;font-family:'DM Sans',sans-serif;color:var(--ink);background:var(--white);outline:none;transition:border-color .2s;text-align:center;} .ans-input:focus{border-color:var(--blue);} /* PRIMARY BTN */ .pbtn{width:100%;padding:14px;border-radius:12px;font-size:16px;font-weight:700;cursor:pointer;border:none;font-family:'DM Sans',sans-serif;transition:all .2s;letter-spacing:.3px;} .pbtn-blue{background:var(--blue);color:#fff;} .pbtn-blue:hover{background:var(--navy);transform:translateY(-1px);} .pbtn-gold{background:var(--gold);color:#fff;} .pbtn-gold:hover{background:#d09020;transform:translateY(-1px);} .pbtn-green{background:var(--green);color:#fff;} .pbtn-green:hover{background:#1d8050;transform:translateY(-1px);} /* TIMER BAR */ .timer-wrap{height:8px;background:var(--line);border-radius:4px;margin-bottom:16px;overflow:hidden;} .timer-bar{height:100%;border-radius:4px;transition:width .1s linear;background:var(--gold);} /* PROGRESS DOTS */ .prog-dots{display:flex;gap:6px;justify-content:center;margin-bottom:16px;} .pdot{width:10px;height:10px;border-radius:50%;background:var(--line);transition:.3s;} .pdot.done{background:var(--green);} .pdot.cur{background:var(--gold);} .pdot.fail{background:var(--red);} /* RESULT CARD */ .result-box{text-align:center;padding:28px 20px;} .result-emoji{font-size:60px;margin-bottom:12px;} .result-score{font-family:'Fraunces',serif;font-size:52px;font-weight:900;line-height:1;} .result-label{font-size:16px;font-weight:700;margin:8px 0 4px;} .result-desc{font-size:14px;color:var(--muted);line-height:1.6;max-width:280px;margin:0 auto;} /* HISTORY BARS */ .hist-row{display:flex;align-items:center;gap:10px;margin-bottom:10px;} .hist-day{font-size:12px;color:var(--muted);font-weight:700;width:36px;flex-shrink:0;} .hist-bar-wrap{flex:1;height:20px;background:var(--line);border-radius:6px;overflow:hidden;} .hist-bar{height:100%;border-radius:6px;transition:width .6s ease;} .hist-val{font-size:12px;font-weight:700;width:32px;text-align:right;flex-shrink:0;} /* ORIENTATION QUIZ */ .orient-q{font-size:18px;font-weight:700;margin-bottom:16px;line-height:1.5;} .orient-opts{display:flex;flex-direction:column;gap:10px;} .orient-opt{padding:13px 16px;border-radius:12px;border:2px solid var(--line);background:var(--white);cursor:pointer;font-family:'DM Sans',sans-serif;font-size:15px;font-weight:600;text-align:left;transition:all .2s;} .orient-opt:hover{border-color:var(--blue);background:var(--blue-soft);} .orient-opt.correct{border-color:var(--green);background:var(--green-soft);color:var(--green);} .orient-opt.wrong{border-color:var(--red);background:var(--red-soft);color:var(--red);} /* WORD GRID */ .word-grid{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-bottom:16px;} .word-pill{padding:10px 18px;background:var(--navy);color:#fff;border-radius:30px;font-size:17px;font-weight:700;font-family:'Fraunces',serif;letter-spacing:1px;} /* PATTERN */ .pattern-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:16px;} .pat-cell{height:64px;border-radius:12px;background:var(--line);border:2px solid transparent;cursor:pointer;transition:all .3s;display:flex;align-items:center;justify-content:center;font-size:28px;} .pat-cell.lit{background:var(--gold);border-color:var(--gold);transform:scale(1.05);} .pat-cell.user-lit{background:var(--blue);border-color:var(--blue);} .pat-cell.correct-cell{background:var(--green);border-color:var(--green);} .pat-cell.wrong-cell{background:var(--red);border-color:var(--red);} /* BOTTOM NAV */ .bnav-wrap{position:fixed;bottom:0;left:0;right:0;background:var(--white);border-top:1.5px solid var(--line);padding:5px 0 6px;display:flex;justify-content:space-around;z-index:100;box-shadow:0 -4px 24px rgba(26,39,68,0.07);} .bnav{display:flex;flex-direction:column;align-items:center;gap:2px;padding:5px 14px;border-radius:12px;cursor:pointer;border:none;background:none;font-family:'DM Sans',sans-serif;color:var(--muted);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;transition:.2s;} .bnav .bi{font-size:19px;} .bnav.on{color:var(--blue);background:var(--blue-soft);} /* TOAST */ .toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);background:var(--navy);color:#fff;padding:12px 24px;border-radius:30px;font-size:14px;font-weight:700;z-index:999;opacity:0;transition:all .35s;white-space:nowrap;pointer-events:none;box-shadow:var(--shadow-lg);} .toast.show{opacity:1;transform:translateX(-50%) translateY(0);} /* RISK METER */ .risk-meter{border-radius:16px;overflow:hidden;margin-bottom:14px;} .risk-track{height:20px;border-radius:10px;background:linear-gradient(to right,var(--green),var(--gold),var(--red));position:relative;} .risk-needle{position:absolute;top:-4px;width:4px;height:28px;background:var(--navy);border-radius:2px;transform:translateX(-50%);transition:left 1s ease;} /* SYMPTOM LIST */ .sym-list{display:flex;flex-direction:column;gap:10px;} .sym-item{display:flex;align-items:center;gap:12px;padding:13px 16px;background:var(--white);border-radius:14px;box-shadow:var(--shadow);border:1.5px solid var(--line);cursor:pointer;transition:all .2s;} .sym-item.sym-yes{border-color:var(--orange);background:var(--orange-soft);} .sym-ico{width:38px;height:38px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;} .sym-info{flex:1;} .sym-name{font-size:14px;font-weight:700;} .sym-desc{font-size:12px;color:var(--muted);margin-top:2px;} .sym-toggle{width:26px;height:26px;border-radius:50%;border:2px solid var(--line);display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;transition:.2s;} .sym-item.sym-yes .sym-toggle{background:var(--orange);border-color:var(--orange);color:#fff;} @keyframes pop{0%,100%{transform:scale(1)}50%{transform:scale(1.15)}} .pop{animation:pop .3s ease;} @keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-6px)}75%{transform:translateX(6px)}} .shake{animation:shake .4s ease;}
  🧠
  MindCheck
    <small>Early Dementia Warning App</small>


🔥 <span id="streakNum">1</span> day streak


<span>🏠</span>Home
<span>🧩</span>Memory
<span>🔵</span>Pattern
<span>📅</span>Orientation
<span>📋</span>Symptoms
<span>📈</span>History













    —
    / 100


Today's Cognitive Score
Complete today's tests to see your score and track your brain health.




📋 Today's Tests





🎯 Risk Level
Based on your recent 7-day performance






  <span>Low Risk</span>
  <span>Moderate</span>
  <span>High Risk</span>


  Complete your daily tests to calculate your risk level accurately.





🧠 What is Dementia?

  <p>Dementia is not a normal part of aging. It is a condition where the brain gradually loses the ability to remember, think clearly, and make decisions.</p>
  <p>The most common type is <strong>Alzheimer's disease</strong>. It affects millions of seniors worldwide. <strong>Early detection</strong> is the key — caught early, it can be managed much better.</p>
  <p>This app tests your <strong>memory, pattern recognition, and orientation</strong> daily — the three areas most affected in early dementia.</p>

✅ This app does NOT diagnose. It helps you notice early changes and talk to your doctor.
Enter fullscreen mode Exit fullscreen mode

Memory TestRemember words, then recall them

Pattern MemoryWatch the pattern, then repeat it

Orientation TestSimple questions about time, place & self

Symptom CheckTap any symptoms you or a family member have noticed
⚠️ Note: These symptoms alone don't confirm dementia. Many have other causes. Always discuss with a doctor.

Score HistoryYour cognitive performance over time

📊 Last 7 Days



💡 Understanding Your Scores

  <p>✅ <strong>80–100:</strong> Excellent cognitive function. Keep up your daily tests!</p>
  <p>🟡 <strong>60–79:</strong> Good, but worth monitoring. Look for patterns.</p>
  <p>🟠 <strong>40–59:</strong> Moderate. Consider discussing with a doctor if this persists.</p>
  <p>🔴 <strong>Below 40:</strong> Concerning. Please consult a doctor soon.</p>
  <p>💙 One bad day is normal. A consistent downward trend over 2+ weeks is what matters.</p>



🚨 When to See a Doctor

  <p>• Scores consistently below 50 for more than 2 weeks</p>
  <p>• Family members notice significant memory changes</p>
  <p>• Getting lost in familiar places</p>
  <p>• Forgetting names of close family members</p>
  <p>• Difficulty managing daily tasks like cooking or bills</p>
  <p>• Repeated conversations or questions within minutes</p>
Enter fullscreen mode Exit fullscreen mode

🏠Home
🧩Memory
🔵Pattern
📅Orient
📋Symptoms
📈History

// ── UTILS ──
function toast(msg,dur=2600){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),dur);}
function go(name,tb,bb){
document.querySelectorAll('.page').forEach(p=>p.classList.remove('on'));
document.getElementById('page-'+name).classList.add('on');
document.querySelectorAll('.tab,.bnav').forEach(b=>b.classList.remove('on'));
const idx=['home','memory','pattern','orient','symptoms','history'].indexOf(name);
document.querySelectorAll('.tab')[idx]?.classList.add('on');
document.querySelectorAll('.bnav')[idx]?.classList.add('on');
window.scrollTo({top:0,behavior:'smooth'});
if(name==='memory') initMemory();
if(name==='pattern') initPattern();
if(name==='orient') initOrient();
if(name==='history') renderHistory();
if(name==='symptoms') renderSymptoms();
}

// ── STATE ──
let scores = {memory:null, pattern:null, orient:null};
let history = [72,85,68,91,78,null,null]; // last 7 days (null = not done)
let symSelected = new Set();

// ── HOME ──
function updateHome(){
const vals = Object.values(scores).filter(v=>v!==null);
const avg = vals.length ? Math.round(vals.reduce((a,b)=>a+b,0)/vals.length) : null;
const circle = document.getElementById('scoreCircle');
const numEl = document.getElementById('homeScore');
const descEl = document.getElementById('scoreDesc');
if(avg!==null){
const offset = 339.3 - (339.3 * avg/100);
circle.style.strokeDashoffset = offset;
circle.style.stroke = avg>=80?'#2DA068':avg>=60?'#E8A838':avg>=40?'#E07030':'#D04040';
numEl.textContent = avg;
descEl.textContent = avg>=80?'Excellent! Your cognitive function looks great today. Keep it up! 🎉':avg>=60?'Good performance. Continue monitoring daily. 👍':avg>=40?'Moderate score. Try again tomorrow and consider speaking to a doctor if this continues.':'Score is low today. Please consult your doctor and don\'t worry — one day doesn\'t define everything.';
// update risk needle
const riskPct = Math.max(5, Math.min(95, 100 - avg));
document.getElementById('riskNeedle').style.left = riskPct+'%';
const advice = avg>=80?'✅ Low risk. Your memory and cognitive scores are in a healthy range. Keep doing these daily tests!':avg>=60?'⚠️ Moderate. Some areas need attention. Stay consistent with daily testing and maintain a healthy lifestyle.':'🚨 Higher risk detected. Please discuss these results with your doctor. Bring this app\'s history to your appointment.';
document.getElementById('riskAdvice').textContent = advice;
}
renderTaskList();
}

function renderTaskList(){
const tasks=[
{key:'memory',icon:'🧩',name:'Word Memory Test',sub:'Remember & recall words'},
{key:'pattern',icon:'🔵',name:'Pattern Memory Test',sub:'Watch & repeat sequences'},
{key:'orient',icon:'📅',name:'Orientation Test',sub:'Time, place & awareness'},
];
document.getElementById('taskList').innerHTML=tasks.map(t=>{
const done=scores[t.key]!==null;
return &lt;div style="display:flex;align-items:center;gap:12px;padding:12px 14px;background:${done?'var(--green-soft)':'var(--bg)'};border-radius:14px;border:1.5px solid ${done?'var(--green)':'var(--line)'}"&gt;
&lt;div style="width:40px;height:40px;border-radius:12px;background:${done?'var(--green)':'var(--line)'};display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0"&gt;${done?'✅':t.icon}&lt;/div&gt;
&lt;div style="flex:1"&gt;&lt;div style="font-weight:700;font-size:14px;${done?'text-decoration:line-through;opacity:.6':''}"&gt;${t.name}&lt;/div&gt;&lt;div style="font-size:12px;color:var(--muted)"&gt;${done?'Score: '+scores[t.key]+'/100':t.sub}&lt;/div&gt;&lt;/div&gt;
&lt;button onclick="go('${t.key}')" style="padding:7px 14px;border-radius:8px;border:none;background:${done?'var(--green)':'var(--blue)'};color:#fff;font-size:12px;font-weight:700;cursor:pointer;font-family:'DM Sans',sans-serif"&gt;${done?'Redo':'Start'}&lt;/button&gt;
&lt;/div&gt;
;
}).join('');
}

// ── MEMORY GAME ──
const wordSets = [
['APPLE','RIVER','CHAIR','MOON','BREAD'],
['HOUSE','CLOCK','FLOWER','BIRD','PAPER'],
['TREE','WATER','BRIDGE','LAMP','BOOK'],
['CLOUD','MUSIC','TABLE','GARDEN','FISH'],
['DOOR','HONEY','MIRROR','STAR','SHOE'],
];
let memState = {phase:'show',words:[],input:[],score:0,timer:null,timeLeft:0};

function initMemory(){
const wset = wordSets[Math.floor(Math.random()*wordSets.length)];
memState = {phase:'show',words:wset,input:[],score:0,timer:null,timeLeft:5};
renderMemory();
}

function renderMemory(){
const el = document.getElementById('memoryContent');
if(memState.phase==='show'){
el.innerHTML=
&lt;div class="notice ni"&gt;&lt;b&gt;📖 How it works:&lt;/b&gt; Study the 5 words below carefully. You have &lt;strong&gt;10 seconds&lt;/strong&gt;. Then you will need to type them from memory.&lt;/div&gt;
&lt;div class="game-box"&gt;
&lt;div class="game-title"&gt;Remember These Words&lt;/div&gt;
&lt;div class="game-sub"&gt;Read each word carefully. You have 10 seconds.&lt;/div&gt;
&lt;div class="timer-wrap"&gt;&lt;div class="timer-bar" id="memTimerBar" style="width:100%"&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="word-grid" id="wordGrid"&gt;
${memState.words.map(w=&gt;
<div class="word-pill">${w}</div>).join('')}
&lt;/div&gt;
&lt;div id="memCountdown" style="text-align:center;font-family:'Fraunces',serif;font-size:32px;font-weight:900;color:var(--navy);margin-top:8px"&gt;10&lt;/div&gt;
&lt;/div&gt;
;
let t=10;
memState.timer=setInterval(()=>{
t--;
const el2=document.getElementById('memCountdown');
const bar=document.getElementById('memTimerBar');
if(el2) el2.textContent=t;
if(bar) bar.style.width=(t/10*100)+'%';
if(t<=0){clearInterval(memState.timer);memState.phase='recall';renderMemory();}
},1000);
} else if(memState.phase==='recall'){
el.innerHTML=
&lt;div class="notice nw"&gt;&lt;b&gt;✏️ Now recall:&lt;/b&gt; Type all 5 words you remember, one by one. Spelling matters — but not capital letters.&lt;/div&gt;
&lt;div class="game-box"&gt;
&lt;div class="game-title"&gt;What Were the Words?&lt;/div&gt;
&lt;div class="game-sub"&gt;Type each word and press Enter or tap Add. (${memState.input.length}/5 entered)&lt;/div&gt;
&lt;div style="display:flex;flex-wrap:wrap;gap:8px;min-height:48px;margin-bottom:16px;padding:10px;background:var(--bg);border-radius:12px" id="recallPills"&gt;
${memState.input.map(w=&gt;
<div style="padding:8px 16px;background:var(--blue);color:#fff;border-radius:20px;font-weight:700;font-size:14px">${w}</div>).join('')}
&lt;/div&gt;
&lt;div style="display:flex;gap:10px"&gt;
&lt;input class="ans-input" id="recallInput" placeholder="Type a word..." style="flex:1" onkeydown="if(event.key==='Enter')addRecallWord()"&gt;
&lt;button class="pbtn pbtn-blue" onclick="addRecallWord()" style="width:auto;padding:14px 20px"&gt;Add&lt;/button&gt;
&lt;/div&gt;
${memState.input.length&gt;0?
<button class="pbtn pbtn-gold" onclick="submitMemory()" style="margin-top:12px">✓ Submit All Words</button>:''}
&lt;/div&gt;
;
setTimeout(()=>document.getElementById('recallInput')?.focus(),100);
} else if(memState.phase==='result'){
const correct = memState.input.filter(w=>memState.words.includes(w.toUpperCase())).length;
const score = Math.round((correct/5)*100);
scores.memory = score;
updateHome();
const emoji = score===100?'🎉':score>=80?'😊':score>=60?'🙂':score>=40?'😟':'😢';
el.innerHTML=
&lt;div class="game-box"&gt;
&lt;div class="result-box"&gt;
&lt;div class="result-emoji"&gt;${emoji}&lt;/div&gt;
&lt;div class="result-score" style="color:${score&gt;=80?'var(--green)':score&gt;=60?'var(--gold)':score&gt;=40?'var(--orange)':'var(--red)'}"&gt;${score}&lt;/div&gt;
&lt;div class="result-label"&gt;Memory Score&lt;/div&gt;
&lt;div class="result-desc"&gt;You recalled &lt;strong&gt;${correct} out of 5&lt;/strong&gt; words correctly.&lt;br&gt;${score&gt;=80?'Excellent memory! Well done.':score&gt;=60?'Good recall. Keep practicing daily.':score&gt;=40?'Some difficulty remembering. This is worth monitoring.':'Significant memory challenge. Please speak to a doctor if this continues.'}&lt;/div&gt;
&lt;/div&gt;
&lt;div style="margin-top:4px"&gt;
&lt;div style="font-weight:700;font-size:14px;margin-bottom:10px"&gt;The words were:&lt;/div&gt;
&lt;div style="display:flex;flex-wrap:wrap;gap:8px"&gt;
${memState.words.map(w=&gt;{
const got=memState.input.map(i=&gt;i.toUpperCase()).includes(w);
return
<div style="padding:8px 14px;border-radius:20px;font-weight:700;font-size:14px;background:${got?'var(--green-soft)':'var(--red-soft)'};color:${got?'var(--green)':'var(--red)'}">${got?'✓':' '} ${w}</div>;
}).join('')}
&lt;/div&gt;
&lt;/div&gt;
&lt;button class="pbtn pbtn-blue" onclick="initMemory()" style="margin-top:16px"&gt;Try Again&lt;/button&gt;
&lt;/div&gt;
;
}
}

function addRecallWord(){
const inp = document.getElementById('recallInput');
if(!inp) return;
const val = inp.value.trim().toUpperCase();
if(!val){toast('⚠️ Please type a word first.');return;}
if(memState.input.includes(val)){toast('You already added that word!');inp.value='';return;}
memState.input.push(val);
inp.value='';
if(memState.input.length>=5){memState.phase='result';renderMemory();}
else renderMemory();
}

function submitMemory(){
memState.phase='result';
renderMemory();
}

// ── PATTERN GAME ──
let patState={seq:[],userSeq:[],step:0,showing:false,level:1,maxLevel:5,score:0,done:false};
const EMOJIS=['🔴','🔵','🟡','🟢','🟣','🟠'];

function initPattern(){
patState={seq:[],userSeq:[],step:0,showing:false,level:1,maxLevel:5,score:0,done:false};
renderPattern();
setTimeout(()=>startPatternRound(),600);
}

function startPatternRound(){
patState.seq.push(Math.floor(Math.random()*6));
patState.userSeq=[];
patState.showing=true;
renderPattern();
showPatternSeq();
}

function showPatternSeq(){
let i=0;
const show=()=>{
if(i>=patState.seq.length){
patState.showing=false;
renderPattern();
return;
}
const idx=patState.seq[i];
const cell=document.querySelector(.pat-cell[data-idx="${idx}"]);
if(cell){cell.classList.add('lit');setTimeout(()=>{cell.classList.remove('lit');i++;setTimeout(show,300);},500);}
else{i++;setTimeout(show,300);}
};
setTimeout(show,500);
}

function renderPattern(){
const el=document.getElementById('patternContent');
if(patState.done){
const emoji=patState.score>=80?'🎉':patState.score>=60?'😊':'😟';
el.innerHTML=&lt;div class="game-box"&gt;&lt;div class="result-box"&gt;
&lt;div class="result-emoji"&gt;${emoji}&lt;/div&gt;
&lt;div class="result-score" style="color:${patState.score&gt;=80?'var(--green)':patState.score&gt;=60?'var(--gold)':'var(--red)'}"&gt;${patState.score}&lt;/div&gt;
&lt;div class="result-label"&gt;Pattern Score&lt;/div&gt;
&lt;div class="result-desc"&gt;You completed &lt;strong&gt;${patState.level-1} of ${patState.maxLevel}&lt;/strong&gt; pattern levels.&lt;br&gt;${patState.score&gt;=80?'Excellent pattern memory!':patState.score&gt;=60?'Good visual memory. Keep practicing.':'Pattern memory needs attention. Daily practice helps.'}&lt;/div&gt;
&lt;button class="pbtn pbtn-blue" onclick="initPattern()" style="margin-top:16px"&gt;Try Again&lt;/button&gt;
&lt;/div&gt;&lt;/div&gt;
;
scores.pattern=patState.score;updateHome();return;
}
el.innerHTML=
&lt;div class="notice ni"&gt;&lt;b&gt;👁️ Watch&lt;/b&gt; the pattern light up, then &lt;b&gt;repeat it&lt;/b&gt; in the same order by tapping the circles.&lt;/div&gt;
&lt;div class="game-box"&gt;
&lt;div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px"&gt;
&lt;div&gt;&lt;div class="game-title" style="font-size:16px"&gt;Level ${patState.level} of ${patState.maxLevel}&lt;/div&gt;&lt;div style="font-size:13px;color:var(--muted)"&gt;${patState.showing?'👀 Watch carefully...':'👆 Now repeat the pattern!'}&lt;/div&gt;&lt;/div&gt;
&lt;span class="chip ${patState.showing?'cy':'cb'}"&gt;${patState.showing?'Watching':'Your turn'}&lt;/span&gt;
&lt;/div&gt;
&lt;div class="prog-dots"&gt;${Array.from({length:patState.maxLevel},(_,i)=&gt;
<div class="pdot ${i<patState.level-1?'done':i===patState.level-1?'cur':''}"></div>).join('')}&lt;/div&gt;
&lt;div class="pattern-grid"&gt;
${EMOJIS.map((e,i)=&gt;
<div class="pat-cell" data-idx="${i}" onclick="patTap(${i})" style="pointer-events:${patState.showing?'none':'auto'}">${e}</div>).join('')}
&lt;/div&gt;
&lt;div style="text-align:center;font-size:13px;color:var(--muted)"&gt;Progress: ${patState.userSeq.length} / ${patState.seq.length} taps&lt;/div&gt;
&lt;/div&gt;
;
}

function patTap(idx){
if(patState.showing||patState.done) return;
patState.userSeq.push(idx);
const cell=document.querySelector(.pat-cell[data-idx="${idx}"]);
if(cell){cell.classList.add('user-lit');setTimeout(()=>cell.classList.remove('user-lit'),300);}
const pos=patState.userSeq.length-1;
if(patState.userSeq[pos]!==patState.seq[pos]){
// Wrong
if(cell){cell.classList.add('wrong-cell');setTimeout(()=>cell.classList.remove('wrong-cell'),500);}
toast('❌ Wrong order! Moving to next level...');
setTimeout(()=>{
if(patState.level>=patState.maxLevel){patState.done=true;patState.score=Math.round(((patState.level-1)/patState.maxLevel)*100);renderPattern();}
else{patState.level++;startPatternRound();}
},800);
return;
}
if(patState.userSeq.length===patState.seq.length){
// Correct!
toast('✅ Correct! Next level...');
setTimeout(()=>{
if(patState.level>=patState.maxLevel){patState.done=true;patState.score=100;renderPattern();}
else{patState.level++;startPatternRound();}
},800);
}
}

// ── ORIENTATION TEST ──
const now2=new Date();
const days=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const months=['January','February','March','April','May','June','July','August','September','October','November','December'];
const seasons=['Winter','Spring','Summer','Autumn'];
function getSeason(m){return m<2||m===11?0:m<5?1:m<8?2:3;}

function makeWrongOptions(correct, pool){
const opts=[correct];
while(opts.length<4){const r=pool[Math.floor(Math.random()*pool.length)];if(!opts.includes(r))opts.push(r);}
return opts.sort(()=>Math.random()-.5);
}

const orientQuestions=[
{q:'What day of the week is it today?',correct:days[now2.getDay()],options:makeWrongOptions(days[now2.getDay()],days)},
{q:'What month is it?',correct:months[now2.getMonth()],options:makeWrongOptions(months[now2.getMonth()],months)},
{q:'What year is it?',correct:String(now2.getFullYear()),options:makeWrongOptions(String(now2.getFullYear()),[String(now2.getFullYear()-2),String(now2.getFullYear()-1),String(now2.getFullYear()+1),String(now2.getFullYear()+2)])},
{q:'What season is it currently?',correct:seasons[getSeason(now2.getMonth())],options:makeWrongOptions(seasons[getSeason(now2.getMonth())],seasons)},
{q:'Approximately what time of day is it?',correct:now2.getHours()<12?'Morning':now2.getHours()<17?'Afternoon':'Evening or Night',options:['Morning','Afternoon','Evening or Night','Midnight'].sort(()=>Math.random()-.5)},
];

let orientState={qIdx:0,answers:[],done:false};

function initOrient(){
orientState={qIdx:0,answers:[],done:false};
renderOrient();
}

function renderOrient(){
const el=document.getElementById('orientContent');
if(orientState.done){
const correct=orientState.answers.filter(Boolean).length;
const score=Math.round((correct/orientQuestions.length)*100);
scores.orient=score;updateHome();
const emoji=score===100?'🎉':score>=80?'😊':score>=60?'🙂':score>=40?'😟':'😢';
el.innerHTML=&lt;div class="game-box"&gt;&lt;div class="result-box"&gt;
&lt;div class="result-emoji"&gt;${emoji}&lt;/div&gt;
&lt;div class="result-score" style="color:${score&gt;=80?'var(--green)':score&gt;=60?'var(--gold)':'var(--red)'}"&gt;${score}&lt;/div&gt;
&lt;div class="result-label"&gt;Orientation Score&lt;/div&gt;
&lt;div class="result-desc"&gt;You answered &lt;strong&gt;${correct} of ${orientQuestions.length}&lt;/strong&gt; correctly.&lt;br&gt;${score===100?'Perfect orientation! Excellent awareness.':score&gt;=80?'Good orientation. Minor lapses are normal.':score&gt;=60?'Some orientation difficulty. Worth monitoring.':'Significant orientation difficulty. Please speak to a doctor.'}&lt;/div&gt;
&lt;button class="pbtn pbtn-blue" onclick="initOrient()" style="margin-top:16px"&gt;Try Again&lt;/button&gt;
&lt;/div&gt;
&lt;div style="margin-top:4px"&gt;
${orientQuestions.map((q,i)=&gt;

<div style="padding:12px 14px;border-radius:12px;margin-bottom:8px;background:${orientState.answers[i]?'var(--green-soft)':'var(--red-soft)'}">
<div style="font-size:13px;font-weight:700;color:${orientState.answers[i]?'var(--green)':'var(--red)'}">${orientState.answers[i]?'✓':'✗'} ${q.q}</div>
<div style="font-size:12px;margin-top:3px;color:var(--muted)">Correct answer: <strong>${q.correct}</strong></div>
</div>).join('')}
&lt;/div&gt;&lt;/div&gt;
;
return;
}
const q=orientQuestions[orientState.qIdx];
el.innerHTML=
&lt;div class="game-box"&gt;
&lt;div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px"&gt;
&lt;span class="chip cb"&gt;Question ${orientState.qIdx+1} of ${orientQuestions.length}&lt;/span&gt;
&lt;span style="font-size:13px;color:var(--muted);font-weight:600"&gt;${orientState.answers.filter(Boolean).length} correct so far&lt;/span&gt;
&lt;/div&gt;
&lt;div class="prog-dots"&gt;${orientQuestions.map((_,i)=&gt;
<div class="pdot ${i<orientState.qIdx?'done':i===orientState.qIdx?'cur':''}"></div>).join('')}&lt;/div&gt;
&lt;div class="orient-q"&gt;${q.q}&lt;/div&gt;
&lt;div class="orient-opts"&gt;
${q.options.map(opt=&gt;
<button class="orient-opt" onclick="answerOrient('${opt}')">${opt}</button>).join('')}
&lt;/div&gt;
&lt;/div&gt;
;
}

function answerOrient(ans){
const q=orientQuestions[orientState.qIdx];
const correct=ans===q.correct;
orientState.answers.push(correct);
// Flash feedback
document.querySelectorAll('.orient-opt').forEach(b=>{
if(b.textContent===q.correct) b.classList.add('correct');
if(b.textContent===ans && !correct) b.classList.add('wrong');
b.disabled=true;
});
setTimeout(()=>{
orientState.qIdx++;
if(orientState.qIdx>=orientQuestions.length) orientState.done=true;
renderOrient();
},900);
}

// ── SYMPTOMS ──
const symptoms=[
{e:'🧠',n:'Forgetting recent events',d:'Forgetting things that just happened — not just names, but whole conversations or events.',bg:'#FEF3EC'},
{e:'🔁',n:'Repeating questions',d:'Asking the same question or telling the same story multiple times in one conversation.',bg:'#FEF3EC'},
{e:'🗺️',n:'Getting lost in familiar places',d:'Losing your way in a neighborhood you know well, or forgetting how to get home.',bg:'#FDEEEE'},
{e:'🗣️',n:'Struggling to find words',d:'Stopping mid-sentence, using the wrong words, or calling things by the wrong name.',bg:'#FEF7E8'},
{e:'💸',n:'Difficulty managing money/bills',d:'Trouble with simple calculations, paying bills, or managing bank accounts.',bg:'#FEF7E8'},
{e:'😕',n:'Confusion about time/date',d:'Not knowing what day, month, or year it is without checking.',bg:'#FDEEEE'},
{e:'😤',n:'Mood or personality changes',d:'Becoming easily upset, suspicious, anxious, or withdrawn without clear reason.',bg:'#FEF3EC'},
{e:'🍳',n:'Trouble with daily tasks',d:'Difficulty doing tasks that were easy before — cooking, dressing, using phone.',bg:'#FEF7E8'},
{e:'👁️',n:'Vision or spatial problems',d:'Difficulty judging distance, reading, or recognizing faces of familiar people.',bg:'#FEF3EC'},
{e:'😴',n:'Sleeping too much or too little',d:'Major changes in sleep pattern — much more or much less than usual.',bg:'#F2EDFB'},
];

function renderSymptoms(){
document.getElementById('symList').innerHTML=symptoms.map((s,i)=>
&lt;div class="sym-item ${symSelected.has(i)?'sym-yes':''}" onclick="toggleSym(${i})" id="sym${i}"&gt;
&lt;div class="sym-ico" style="background:${s.bg}"&gt;${s.e}&lt;/div&gt;
&lt;div class="sym-info"&gt;&lt;div class="sym-name"&gt;${s.n}&lt;/div&gt;&lt;div class="sym-desc"&gt;${s.d}&lt;/div&gt;&lt;/div&gt;
&lt;div class="sym-toggle"&gt;${symSelected.has(i)?'✓':''}&lt;/div&gt;
&lt;/div&gt;
).join('');
updateSymResult();
}

function toggleSym(i){
if(symSelected.has(i)) symSelected.delete(i); else symSelected.add(i);
renderSymptoms();
}

function updateSymResult(){
const n=symSelected.size;
const el=document.getElementById('symResultBox');
if(n===0){el.innerHTML='';return;}
const level=n<=2?'low':n<=5?'moderate':'high';
const bg=level==='low'?'ng':level==='moderate'?'nw':'nd';
const msg=level==='low'?&lt;b&gt;✅ Low concern.&lt;/b&gt; 1–2 symptoms can have many causes (stress, poor sleep, illness). Monitor for changes but no immediate alarm.:level==='moderate'?&lt;b&gt;⚠️ Moderate concern.&lt;/b&gt; You noticed ${n} symptoms. This is worth discussing with a doctor, especially if symptoms have been present for more than a few weeks.:&lt;b&gt;🚨 Please see a doctor.&lt;/b&gt; You identified ${n} significant symptoms. While this doesn't confirm dementia, a medical evaluation is strongly recommended. Early detection leads to better outcomes.;
el.innerHTML=&lt;div class="notice ${bg}"&gt;${msg}&lt;/div&gt;
${n&gt;=3?
<div class="card" style="border-left:4px solid var(--blue)"><div style="font-weight:700;margin-bottom:8px">📋 What to tell your doctor</div><div style="font-size:14px;line-height:1.7;color:#444">Print or show this list to your doctor:<br><br>${[...symSelected].map(i=>'• '+symptoms[i].n).join('<br>')}</div></div>:''};
}

// ── HISTORY ──
function renderHistory(){
const dayLabels=['7d ago','6d ago','5d ago','4d ago','3d ago','Yesterday','Today'];
const maxVal=100;
document.getElementById('histBars').innerHTML=history.map((v,i)=>{
if(v===null) return &lt;div class="hist-row"&gt;&lt;div class="hist-day"&gt;${dayLabels[i]}&lt;/div&gt;&lt;div class="hist-bar-wrap"&gt;&lt;div style="font-size:12px;color:var(--muted);padding:2px 8px"&gt;Not done&lt;/div&gt;&lt;/div&gt;&lt;div class="hist-val" style="color:var(--muted)"&gt;—&lt;/div&gt;&lt;/div&gt;;
const color=v>=80?'var(--green)':v>=60?'var(--gold)':v>=40?'var(--orange)':'var(--red)';
return &lt;div class="hist-row"&gt;
&lt;div class="hist-day"&gt;${dayLabels[i]}&lt;/div&gt;
&lt;div class="hist-bar-wrap"&gt;&lt;div class="hist-bar" style="width:${v}%;background:${color}"&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="hist-val" style="color:${color}"&gt;${v}&lt;/div&gt;
&lt;/div&gt;
;
}).join('');
}

// ── INIT ──
renderTaskList();
renderSymptoms();
renderHistory();

How I Built It

<I chose a lightweight, "Vanilla" tech stack to ensure the app runs smoothly on older smartphones often used by the elderly:

HTML5: Structured for accessibility and semantic clarity.

CSS3 (Custom Properties & Flexbox): Used to create a calming, professional "medical-grade" UI. I implemented custom CSS animations (like the fadeUp and score-ring transitions) to provide visual cues without being distracting.

JavaScript (ES6): I built a custom state management system to handle the game logic (memory sets, pattern generation, and scoring) without needing a heavy backend.

SVG: Used for the "Score Ring" and "Risk Meter" to ensure graphics remain crisp on all screen sizes.

Google Fonts: Integrated Fraunces for a trustworthy, "editorial" feel for headings and DM Sans for high-legibility body text.

The app is fully responsive and functions as a Progressive Web App (PWA), meaning it can be saved to a phone's home screen for daily use without a complex installation process.>

Top comments (0)