Newer
Older
fast-kognitest / index.html
<!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)}%&emsp;Струп: ${mStroop.toFixed(1)}/5 &emsp; Время: ${mStroopT.toFixed(1)}с</div>
      <div class="stat-row">Реакция: ${mReact?mReact.toFixed(0)+" мс":'–'} &emsp; Слова: ${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>