<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Когнитивный тест</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: sans-serif; background: #f7f7f9; max-width: 440px; margin: 0 auto; padding: 1.5em;} #test-card { background: #fff; border-radius: 10px; padding: 2em; box-shadow: 0 4px 18px #0001;} h2 { text-align: center;} .center { text-align: center; } button, input[type=text] { font-size: 1.1em; margin-top: 1em; padding: 0.5em 1em; } .hidden { display: none; } #stroop-word { font-size: 2em; font-weight: bold; letter-spacing: 2px;} #timer { font-size: 1.2em; color: #888;} .result-block { margin: 1.5em 0; } ul { padding-left: 1.3em; } #stats-block { background: #f3f7ff; border-radius: 7px; margin-top: 1.4em; padding: 1em; font-size:0.97em;} .stat-row { margin: 0.5em 0; } canvas { margin: 0.5em auto; display: block; } @media (max-width:500px) { #test-card {padding: 1em;} } </style> </head> <body> <div id="test-card"> <h2>Быстрый когнитивный тест</h2> <div id="test-content" class="center"></div> </div> <script> // --- UTILS --- function shuffle(arr) { for (let i = arr.length-1; i > 0; i--) { const j = Math.floor(Math.random()*(i+1)); [arr[i],arr[j]]=[arr[j],arr[i]]; } } function randomDigits(len) { return Array.from({length:len},()=>Math.floor(Math.random()*10)); } function randomLetter() { return "АБВГДЕЖЗИКЛМНОПРСТУФХЦЧШЩЭЮЯ".charAt(Math.floor(Math.random()*29)); } function randomColor() { return ['красный','синий','зелёный','жёлтый','фиолетовый'][Math.floor(Math.random()*5)]; } function colorToHex(color) { return {красный:'#e74c3c',синий:'#3498db',зелёный:'#27ae60',жёлтый:'#f7b731',фиолетовый:'#8f44fd'}[color]||'#000'; } // --- STATISTICS: read/write history from localStorage --- const LS_KEY = 'cogtest_results'; function saveResult(res) { let data = []; try { data = JSON.parse(localStorage.getItem(LS_KEY)) || []; } catch(e){} data.push(res); localStorage.setItem(LS_KEY, JSON.stringify(data)); } function loadResults() { try { return JSON.parse(localStorage.getItem(LS_KEY)) || []; } catch(e) { return []; } } function clearResults() { localStorage.removeItem(LS_KEY); } function statMean(arr) { if(!arr.length) return 0; return arr.reduce((a,b)=>a+b,0)/arr.length; } function statStd(arr) { if(arr.length < 2) return 0; let mean = statMean(arr); return Math.sqrt(arr.reduce((s,x)=>s+Math.pow(x-mean,2),0)/arr.length); } function dateStr(ts) { let d = new Date(ts); return d.toLocaleDateString() + " " + d.toLocaleTimeString().slice(0,5); } // --- TEST STATE --- let state = { step: 0, memory: {seq:[], correct:false, time:0, input:''}, stroop: {total:5, correct:0, time:0}, reaction: {wait:0, click:0}, words: {letter:'', list:[], count:0}, results: {} }; const content = document.getElementById('test-content'); // --- STEPS --- function nextStep() { state.step++; switch(state.step) { case 1: memoryTest(); break; case 2: stroopTest(); break; case 3: reactionTest(); break; case 4: wordGenTest(); break; case 5: showResults(); break; } } // --- MEMORY TEST --- function memoryTest() { state.memory.seq = randomDigits(6); content.innerHTML = `<b>Запомни эту последовательность за 6 секунд:</b> <div style="font-size:2em;letter-spacing:8px;margin:1em 0;">${state.memory.seq.join(' ')}</div> <div id="timer"></div>`; let t=6; document.getElementById('timer').textContent=`${t} сек.`; let timer = setInterval(()=>{ t--; document.getElementById('timer').textContent=`${t} сек.`; if(t===0){ clearInterval(timer); askMemoryInput(); } },1000); } function askMemoryInput() { content.innerHTML = `<b>Введи <span style="color:#c00">обратную</span> последовательность:</b> <input type="text" id="mem-input" placeholder="Пример: 246183"><br> <button onclick="submitMemory()">Готово</button>`; document.getElementById('mem-input').focus(); window.submitMemory = function() { let val = document.getElementById('mem-input').value.replace(/\s/g,''); state.memory.input = val; let correct = state.memory.seq.slice().reverse().join(''); state.memory.correct = val === correct; state.memory.time = Date.now(); nextStep(); } } // --- STROOP TEST --- function stroopTest() { content.innerHTML = `<b>Тест Струпа: укажи <span style="color:#00a">цвет шрифта</span>, не слово.<br>Всего 5 заданий.</b> <div id="stroop-block" style="margin-top:1.5em"></div> <div id="stroop-progress" style="margin:1em;"></div> <div id="timer"></div>`; state.stroop.correct = 0; let rounds = state.stroop.total, current = 0, t0 = Date.now(); function next() { if(current>=rounds) { state.stroop.time = Date.now()-t0; nextStep(); return; } let word = randomColor(), color = randomColor(); while(color===word) color = randomColor(); let colors = ['красный','синий','зелёный','жёлтый','фиолетовый']; shuffle(colors); document.getElementById('stroop-block').innerHTML = `<span id="stroop-word" style="color:${colorToHex(color)}">${word.toUpperCase()}</span><br> ${colors.map(c=>`<button style="margin:0.5em" onclick="stroopAnswer('${c}')">${c}</button>`).join('')}`; document.getElementById('stroop-progress').textContent = `Вопрос ${current+1} из ${rounds}`; window.stroopAnswer = function(ans) { if(ans===color) state.stroop.correct++; current++; next(); } } next(); } // --- REACTION TEST --- function reactionTest() { content.innerHTML = `<b>Тест на скорость реакции</b> <div id="timer" style="margin-top:1.5em;">Жди зелёного цвета…</div> <button id="react-btn" style="margin-top:2em" disabled>...</button>`; let wait = 1200+Math.random()*1800; state.reaction.wait = wait; setTimeout(()=>{ let t0 = Date.now(); let btn = document.getElementById('react-btn'); btn.textContent = "КЛИКАЙ!"; btn.disabled = false; btn.style.background="#27ae60"; btn.style.color="#fff"; btn.onclick = ()=>{ state.reaction.click = Date.now()-t0; nextStep(); } document.getElementById('timer').textContent = "ЖМИ!"; }, wait); } // --- WORD GENERATION TEST --- function wordGenTest() { state.words.letter = randomLetter(); state.words.list = []; content.innerHTML = `<b>За 30 секунд напиши максимум слов на букву <span style="color:#009">${state.words.letter}</span>:</b> <input type="text" id="word-input" placeholder="Вводи слова через пробел" style="width:95%"><br> <button onclick="finishWords()">Готово</button> <div id="timer"></div>`; let t=30; document.getElementById('timer').textContent=`${t} сек.`; let timer = setInterval(()=>{ t--; document.getElementById('timer').textContent=`${t} сек.`; if(t===0){ clearInterval(timer); finishWords(); } },1000); document.getElementById('word-input').focus(); window.finishWords = function() { clearInterval(timer); let val = document.getElementById('word-input').value.trim(); if(val) state.words.list = val.split(/\s+/).filter(w=>w[0]?.toUpperCase()===state.words.letter); else state.words.list = []; nextStep(); } } // --- RESULTS & INTERPRETATION --- function showResults() { // --- Сохраняем результат в историю const resObj = { time: Date.now(), memory: +state.memory.correct, memoryInput: state.memory.input, memorySeq: state.memory.seq, stroop: state.stroop.correct, stroopTime: +(state.stroop.time/1000).toFixed(2), reaction: state.reaction.click, words: state.words.list.length, wordsList: state.words.list }; saveResult(resObj); // Загружаем историю const all = loadResults(); const last10 = all.slice(-10); const memArr = last10.map(r=>r.memory); const stroopArr = last10.map(r=>r.stroop); const stroopTArr = last10.map(r=>r.stroopTime); const reactArr = last10.map(r=>r.reaction).filter(x=>!!x); const wordsArr = last10.map(r=>r.words); // Средние значения const mMem = statMean(memArr), mStroop = statMean(stroopArr), mStroopT = statMean(stroopTArr); const mReact = statMean(reactArr), mWords = statMean(wordsArr); // --- Интерпретация на основе истории let level = "Всё ок!"; let comment = ""; // критерии: если результат ниже среднего на 1 stddev или явно хуже обычного const stdStroop = statStd(stroopArr); const stdReact = statStd(reactArr); const stdWords = statStd(wordsArr); let warnings = 0; if(state.memory.correct === 0 && mMem > 0.7) { level = "❗ Нарушена память"; warnings++; comment += "Рабочая память ниже твоей нормы. "; } if(state.stroop.correct < mStroop-stdStroop) { level = "❗ Нарушено внимание"; warnings++; comment += "Внимание ниже обычного. "; } if(state.reaction.click && state.reaction.click > mReact+stdReact) { level = "❗ Реакция замедлена"; warnings++; comment += "Реакция медленнее обычного. "; } if(state.words.list.length < mWords-stdWords) { level = "❗ Словесная генерация снижена"; warnings++; comment += "Словесная продукция ниже твоей нормы. "; } if(warnings >= 2) { level = "⚠️ Существенное снижение когнитивных функций"; } // Если наоборот — лучше обычного if(warnings === 0 && ( (state.stroop.correct > mStroop+stdStroop && mStroop!==state.stroop.correct) || (state.words.list.length > mWords+stdWords) || (state.reaction.click && state.reaction.click < mReact-stdReact && mReact!==state.reaction.click) )) { level = "🌟 Лучше, чем обычно!"; comment = "Сегодня твои показатели выше нормы!"; } // --- График динамики результатов function drawGraph(id, arr, color="#2d9cdb", labelFn) { if(arr.length < 2) return ''; const w=170, h=55, max=Math.max(...arr), min=Math.min(...arr), pad=10; let y = v => h-pad-((v-min)/(max-min+0.01))*(h-2*pad); let pts = arr.map((v,i)=>`${(w/(arr.length-1))*i},${y(v)}`).join(' '); let labels = arr.map((v,i)=>labelFn?labelFn(v):v).join(', '); return `<svg width="${w}" height="${h}" style="background:#e6f1fb;border-radius:4px"><polyline points="${pts}" fill="none" stroke="${color}" stroke-width="2"/> <circle cx="${w-pad}" cy="${y(arr[arr.length-1])}" r="3" fill="${color}" /> <text x="4" y="${h-3}" font-size="10" fill="#999">${min}</text> <text x="${w-20}" y="13" font-size="10" fill="#999">${max}</text> </svg>`; } // --- Выводим результат + динамику let res = ` <h3>Результаты:</h3> <div class="result-block"> <b>1. Память:</b> ${state.memory.correct ? "✔️ Верно" : "❌ Ошибка"}<br> <small>Твоя последовательность: ${state.memory.input || "-"}. Правильная: ${state.memory.seq.slice().reverse().join('')}</small> </div> <div class="result-block"> <b>2. Струп:</b> ${state.stroop.correct}/5<br> <span style="color:#888;font-size:0.97em">Время: ${(state.stroop.time/1000).toFixed(1)} сек</span><br> ${drawGraph('stroop', stroopArr, "#d86f33")} </div> <div class="result-block"> <b>3. Реакция:</b> ${state.reaction.click ? state.reaction.click+' мс' : "—"} ${drawGraph('react', reactArr, "#52b447")} </div> <div class="result-block"> <b>4. Слова:</b> ${state.words.list.length} слов на "${state.words.letter}"<br> <span style="color:#888;font-size:0.97em">${state.words.list.join(', ')}</span> ${drawGraph('words', wordsArr, "#0077bb")} </div> <hr> <div style="font-size:1.2em;text-align:center;margin:1em 0;"><b>${level}</b></div> <div class="center" style="color:#555;">${comment}</div> <div id="stats-block"> <div class="stat-row"><b>Средние значения последних 10 тестов:</b></div> <div class="stat-row">Память: ${(mMem*100).toFixed(0)}% Струп: ${mStroop.toFixed(1)}/5   Время: ${mStroopT.toFixed(1)}с</div> <div class="stat-row">Реакция: ${mReact?mReact.toFixed(0)+" мс":'–'}   Слова: ${mWords.toFixed(1)}</div> <div style="margin:0.8em 0;"><button onclick="showHistory()">История попыток</button> <button onclick="window.location.reload()">Пройти снова</button> <button onclick="resetStats()">Очистить статистику</button></div> </div> `; content.innerHTML = res; } // --- SHOW HISTORY --- function showHistory() { const all = loadResults(); if(!all.length) return alert('Нет истории.'); let s = '<h3>История последних попыток</h3><ol style="font-size:0.98em;text-align:left">'; for(let i=all.length-1;i>=Math.max(0,all.length-10);i--) { const r = all[i]; s += `<li>${dateStr(r.time)} | <b>Память:</b> ${r.memory?"✔️":"❌"} <b>Струп:</b> ${r.stroop}/5 <b>Реакция:</b> ${r.reaction? r.reaction+"мс":"–"} <b>Слова:</b> ${r.words}</li>`; } s+='</ol><div class="center"><button onclick="showResults()">Назад</button></div>'; content.innerHTML = s; } // --- RESET STATS --- function resetStats() { if(confirm('Очистить всю статистику?')) { clearResults(); alert('Статистика очищена.'); window.location.reload(); } } // --- START --- content.innerHTML = `<button onclick="nextStep()">Начать тест</button>`; </script> </body> </html>