Загрузка данных


<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест по механике</title>
<style>
  * { margin:0; padding:0; box-sizing:border-box; }
  body {
    font-family: 'Segoe UI', Arial, sans-serif;
    background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
    min-height: 100vh;
    color:#e8eef2;
    display:flex; justify-content:center; align-items:flex-start;
    padding:24px;
  }
  .card {
    background: rgba(255,255,255,0.06);
    border: 1px solid rgba(255,255,255,0.12);
    border-radius:16px;
    padding:28px 32px;
    max-width:760px; width:100%;
    box-shadow: 0 10px 40px rgba(0,0,0,0.4);
    backdrop-filter: blur(6px);
  }
  h1 { font-size:1.6rem; margin-bottom:8px; }
  h2 { font-size:1.25rem; margin:16px 0 12px; }
  .sub { color:#9fb4c0; margin-bottom:16px; font-size:.95rem; }
  .timer {
    position: sticky; top:12px;
    display:inline-block;
    font-size:1.3rem; font-weight:700;
    padding:8px 18px; border-radius:10px;
    background:#1b2b33; border:1px solid rgba(255,255,255,0.15);
  }
  .timer.warn { background:#7a2e12; animation: pulse 1s infinite; }
  @keyframes pulse { 50% { opacity:.6; } }
  .violation {
    margin-top:10px; color:#ffb3a7; font-size:.9rem;
  }
  .q {
    margin:20px 0; padding:16px; border-radius:12px;
    background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08);
  }
  .q .num { font-weight:700; color:#7fd4ff; margin-bottom:6px; }
  .q .text { margin-bottom:10px; line-height:1.45; }
  label.opt {
    display:block; padding:8px 12px; margin:6px 0; border-radius:8px;
    background:rgba(255,255,255,0.05); cursor:pointer; transition:background .15s;
    user-select:none;
  }
  label.opt:hover { background:rgba(127,212,255,0.12); }
  input[type=radio] { margin-right:8px; accent-color:#38b6ff; }
  button {
    font-size:1rem; font-weight:600; padding:12px 28px; border:none;
    border-radius:10px; cursor:pointer; color:#fff;
    background: linear-gradient(90deg,#1e90c9,#38b6ff);
    transition: transform .1s, box-shadow .2s;
  }
  button:hover { transform:translateY(-1px); box-shadow:0 6px 18px rgba(56,182,255,.35); }
  button:disabled { background:#555; cursor:not-allowed; transform:none; box-shadow:none; }
  .overlay {
    position:fixed; inset:0; background:rgba(0,0,0,0.92);
    display:flex; flex-direction:column; justify-content:center; align-items:center;
    z-index:9999; text-align:center; padding:20px;
  }
  .overlay h2 { color:#ff6b57; font-size:1.8rem; }
  .overlay p { max-width:520px; color:#cfd8de; margin:12px 0 20px; line-height:1.5; }
  .hidden { display:none !important; }
  .result-ok { color:#7CFC98; }
  .result-bad { color:#ff8f7a; }
  .rules { text-align:left; margin:14px 0; line-height:1.6; color:#c6d3da; }
  .rules b { color:#7fd4ff; }
  #nameInput {
    width:100%; padding:10px 14px; margin-bottom:14px; border-radius:8px;
    border:1px solid rgba(255,255,255,0.2); background:#14242c; color:#fff; font-size:1rem;
  }
</style>
<base target="_blank">
</head>
<body>

<div class="card" id="startScreen">
  <h1>Тест по механике</h1>
  <p class="sub">Равномерное, равноускоренное и равнозамедленное движение, комбинированные задачи, равномерное движение по окружности.</p>
  <div class="rules">
    <b>Правила:</b>
    <ul style="margin-left:20px">
      <li>На тест даётся <b>60 минут</b>.</li>
      <li>В тесте <b>15 случайных вопросов</b>, варианты ответов перемешаны.</li>
      <li>Запрещено переключаться на другие вкладки, сворачивать окно или копировать текст.</li>
      <li>Каждое нарушение фиксируется и влияет на результат.</li>
      <li>Ответы на вопросы можно менять до нажатия «Завершить тест».</li>
    </ul>
  </div>
  <input type="text" id="nameInput" placeholder="Введите ваше ФИО" autocomplete="off">
  <button id="startBtn">Начать тест</button>
</div>

<div class="card hidden" id="testScreen">
  <div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px;">
    <div class="timer" id="timer">60:00</div>
    <div class="violation" id="violationCount">Нарушений: 0</div>
  </div>
  <div id="questions"></div>
  <button id="finishBtn" style="margin-top:10px;">Завершить тест</button>
</div>

<div class="card hidden" id="resultScreen">
  <h1>Результат</h1>
  <div id="resultBody" style="margin-top:16px; line-height:1.7;"></div>
  <button onclick="location.reload()" style="margin-top:20px;">Пройти заново</button>
</div>

<div class="overlay hidden" id="blockOverlay">
  <h2>⚠️ Нарушение правил!</h2>
  <p id="blockText"></p>
  <button id="blockBtn">Я вернулся, продолжить</button>
</div>

<script>
// ================= БАНК ВОПРОСОВ =================
const BANK = [
  {t:"Тело движется равномерно прямолинейно со скоростью 5 м/с. Какой путь оно пройдёт за 20 с?", a:["100 м","25 м","4 м","10 м"], c:0, topic:"Равномерное движение"},
  {t:"Формула пути при равномерном прямолинейном движении:", a:["S = v·t","S = v/t","S = a·t²","S = v²/(2a)"], c:0, topic:"Равномерное движение"},
  {t:"Автомобиль проехал 120 км за 2 часа. Его средняя скорость равна:", a:["60 км/ч","30 км/ч","240 км/ч","80 км/ч"], c:0, topic:"Равномерное движение"},
  {t:"График зависимости координаты от времени при равномерном движении — это:", a:["прямая линия","парабола","гипербола","окружность"], c:0, topic:"Равномерное движение"},
  {t:"Пешеход шёл 10 минут со скоростью 1,5 м/с. Какой путь он прошёл?", a:["900 м","15 м","150 м","90 м"], c:0, topic:"Равномерное движение"},
  {t:"Начальная скорость тела 2 м/с, ускорение 3 м/с². Чему равна скорость через 4 с?", a:["14 м/с","12 м/с","20 м/с","9 м/с"], c:0, topic:"Равноускоренное движение"},
  {t:"Ускорение — это физическая величина, равная:", a:["отношению изменения скорости ко времени","произведению скорости на время","отношению пути ко времени","отношению времени к скорости"], c:0, topic:"Равноускоренное движение"},
  {t:"Тело начинает движение из покоя с ускорением 2 м/с². Какой путь оно пройдёт за 5 с?", a:["25 м","10 м","50 м","100 м"], c:0, topic:"Равноускоренное движение"},
  {t:"Формула скорости при равноускоренном движении:", a:["v = v₀ + at","v = v₀ − at","v = v₀·a·t","v = S/t"], c:0, topic:"Равноускоренное движение"},
  {t:"Велосипедист разгоняется с ускорением 0,5 м/с², начальная скорость 4 м/с. Скорость через 6 с:", a:["7 м/с","3 м/с","10 м/с","12 м/с"], c:0, topic:"Равноускоренное движение"},
  {t:"Поезд тормозит с начальной скоростью 20 м/с и ускорением −1 м/с². Через какое время он остановится?", a:["20 с","10 с","19 с","21 с"], c:0, topic:"Равнозамедленное движение"},
  {t:"Автомобиль со скоростью 72 км/с останавливается за 5 с. Его ускорение (замедление) равно:", a:["−4 м/с²","−14,4 м/с²","4 м/с²","−1,25 м/с²"], c:0, topic:"Равнозамедленное движение"},
  {t:"Тело движется равнозамедленно. Что происходит со скоростью тела?", a:["уменьшается с постоянным ускорением","увеличивается","остаётся постоянной","меняется хаотично"], c:0, topic:"Равнозамедленное движение"},
  {t:"Мяч брошен вертикально вверх со скоростью 10 м/с (g = 10 м/с²). На какую максимальную высоту он поднимется?", a:["5 м","10 м","20 м","2,5 м"], c:0, topic:"Равнозамедленное движение"},
  {t:"Поезд вышел из пункта А со скоростью 54 км/ч. Через 2 часа из А вышел второй поезд со скоростью 90 км/ч. Через сколько часов после выхода второго поезда он догонит первый?", a:["3 ч","2 ч","5 ч","1,5 ч"], c:0, topic:"Комбинированная задача"},
  {t:"Мотоциклист проехал первую половину пути со скоростью 40 км/ч, а вторую — со скоростью 60 км/ч. Средняя скорость на всём пути:", a:["48 км/ч","50 км/ч","45 км/ч","52 км/ч"], c:0, topic:"Комбинированная задача"},
  {t:"Пешеход 30 минут шёл со скоростью 5 км/ч, а затем ехал на автобусе 1,5 часа со скоростью 60 км/ч. Общий путь равен:", a:["92,5 км","65 км","90 км","95 км"], c:0, topic:"Комбинированная задача"},
  {t:"Автомобиль 2 часа ехал со скоростью 60 км/ч, затем остановился на 30 минут, а потом 1 час ехал со скоростью 80 км/ч. Средняя скорость за всё движение (включая остановку):", a:["57,1 км/ч","56 км/ч","66,7 км/ч","60 км/ч"], c:0, topic:"Комбинированная задача"},
  {t:"Тело 3 с двигалось равномерно со скоростью 2 м/с, а затем разгонялось с ускорением 1 м/с² в течение 4 с. Полный путь тела:", a:["22 м","26 м","24 м","14 м"], c:0, topic:"Комбинированная задача"},
  {t:"При равномерном движении по окружности ускорение тела направлено:", a:["к центру окружности","по касательной к траектории","от центра окружности","скорость постоянна, ускорения нет"], c:0, topic:"Движение по окружности"},
  {t:"Тело движется по окружности радиусом 2 м с периодом 4 с. Линейная скорость тела равна:", a:["π м/с","2π м/с","4π м/с","π/2 м/с"], c:0, topic:"Движение по окружности"},
  {t:"Частота вращения — это:", a:["число оборотов за единицу времени","время одного оборота","отношение радиуса к периоду","скорость движения по окружности"], c:0, topic:"Движение по окружности"},
  {t:"Период обращения равен 0,5 с. Частота вращения равна:", a:["2 Гц","0,5 Гц","5 Гц","1 Гц"], c:0, topic:"Движение по окружности"},
  {t:"При равномерном движении по окружности какая величина остаётся постоянной по модулю?", a:["линейная скорость","центростремительное ускорение по направлению","скорость по направлению","положение тела"], c:0, topic:"Движение по окружности"},
  {t:"Связь между линейной и угловой скоростью (v и ω):", a:["v = ωR","v = ω/R","v = R/ω","v = ωR²"], c:0, topic:"Движение по окружности"},
];

// ================= СОСТОЯНИЕ =================
const TOTAL_TIME = 60 * 60; // 60 минут
let timeLeft = TOTAL_TIME;
let timerId = null;
let questions = [];
let violations = 0;
let testStarted = false;
let blocked = false;
let studentName = "";

function shuffle(arr){
  const a = arr.slice();
  for(let i=a.length-1;i>0;i--){
    const j = Math.floor(Math.random()*(i+1));
    [a[i],a[j]]=[a[j],a[i]];
  }
  return a;
}

function buildTest(){
  const picked = shuffle(BANK).slice(0, 15);
  questions = picked.map(q => {
    const order = shuffle(q.a.map((_,i)=>i));
    return {
      topic: q.topic,
      text: q.t,
      answers: order.map(i => q.a[i]),
      correct: order.indexOf(q.c)
    };
  });
}

function render(){
  const box = document.getElementById('questions');
  box.innerHTML = '';
  questions.forEach((q,qi)=>{
    const div = document.createElement('div');
    div.className = 'q';
    let html = `<div class="num">Вопрос ${qi+1} <span style="color:#5f7d8c;font-weight:400">(${q.topic})</span></div><div class="text">${q.text}</div>`;
    q.answers.forEach((ans,ai)=>{
      html += `<label class="opt"><input type="radio" name="q${qi}" value="${ai}">${ans}</label>`;
    });
    div.innerHTML = html;
    box.appendChild(div);
  });
}

// ================= ТАЙМЕР =================
function updateTimer(){
  const m = Math.floor(timeLeft/60), s = timeLeft%60;
  const t = document.getElementById('timer');
  t.textContent = `${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
  t.classList.toggle('warn', timeLeft <= 300);
}

function startTimer(){
  timerId = setInterval(()=>{
    timeLeft--;
    updateTimer();
    if(timeLeft<=0) finishTest(true);
  },1000);
}

// ================= АНТИСПИСЫВАНИЕ =================
function addViolation(msg){
  if(!testStarted) return;
  violations++;
  document.getElementById('violationCount').textContent = `Нарушений: ${violations}`;
  showBlock(msg);
}

function showBlock(msg){
  blocked = true;
  document.getElementById('blockText').textContent = msg;
  document.getElementById('blockOverlay').classList.remove('hidden');
}

document.addEventListener('visibilitychange', ()=>{
  if(document.hidden) addViolation('Вы перешли на другую вкладку или свернули окно. Это зафиксировано как нарушение №' + violations + '.');
});

window.addEventListener('blur', ()=>{
  if(testStarted && !blocked) addViolation('Окно браузера потеряло фокус. Возможно, вы переключились на другое приложение. Нарушение №' + violations + '.');
});

document.addEventListener('contextmenu', e => { e.preventDefault(); addViolation('Попытка вызвать контекстное меню (копирование). Нарушение №' + violations + '.'); });

document.addEventListener('copy', e => { e.preventDefault(); addViolation('Копирование текста запрещено. Нарушение №' + violations + '.'); });

document.addEventListener('selectstart', e => { if(testStarted) e.preventDefault(); });

document.addEventListener('keydown', e => {
  if(!testStarted) return;
  if((e.ctrlKey||e.metaKey) && ['c','x','u','p','s','v'].includes(e.key.toLowerCase())){
    e.preventDefault();
    addViolation('Комбинация клавиш (Ctrl+' + e.key.toUpperCase() + ') запрещена. Нарушение №' + violations + '.');
  }
  if(e.key==='F12' || (e.ctrlKey && e.shiftKey && ['I','J','C'].includes(e.key.toUpperCase()))){
    e.preventDefault();
    addViolation('Попытка открыть инструменты разработчика. Нарушение №' + violations + '.');
  }
  if(e.altKey){ e.preventDefault(); addViolation('Нажатие Alt запрещено. Нарушение №' + violations + '.'); }
});

// Попытка уйти со страницы во время теста
window.addEventListener('beforeunload', e => {
  if(testStarted && !document.getElementById('resultScreen') || (testStarted && document.getElementById('resultScreen').classList.contains('hidden'))){
    e.preventDefault(); e.returnValue='';
  }
});

document.getElementById('blockBtn').addEventListener('click', ()=>{
  blocked = false;
  document.getElementById('blockOverlay').classList.add('hidden');
});

// ================= СТАРТ И ЗАВЕРШЕНИЕ =================
document.getElementById('startBtn').addEventListener('click', ()=>{
  studentName = document.getElementById('nameInput').value.trim() || 'Без имени';
  buildTest();
  render();
  document.getElementById('startScreen').classList.add('hidden');
  document.getElementById('testScreen').classList.remove('hidden');
  testStarted = true;
  updateTimer();
  startTimer();
  window.scrollTo(0,0);
});

document.getElementById('finishBtn').addEventListener('click', ()=> finishTest(false));

function finishTest(timeUp){
  if(!testStarted) return;
  testStarted = false;
  clearInterval(timerId);
  document.getElementById('blockOverlay').classList.add('hidden');

  let score = 0, correct = 0, wrong = 0, skipped = 0;
  questions.forEach((q,qi)=>{
    const sel = document.querySelector(`input[name="q${qi}"]:checked`);
    if(!sel){ skipped++; return; }
    if(parseInt(sel.value) === q.correct){ correct++; }
    else wrong++;
  });
  // Штраф: 5 баллов за каждое нарушение
  score = Math.max(0, correct*100/questions.length - violations*5);
  score = Math.round(score);

  document.getElementById('testScreen').classList.add('hidden');
  document.getElementById('resultScreen').classList.remove('hidden');

  const verdict = score>=85 ? 'Отлично!' : score>=70 ? 'Хорошо!' : score>=50 ? 'Удовлетворительно' : 'Неудовлетворительно';
  const vc = score>=50 ? 'result-ok' : 'result-bad';

  document.getElementById('resultBody').innerHTML = `
    <p><b>Ученик:</b> ${studentName}</p>
    ${timeUp ? '<p style="color:#ffb3a7">⏰ Время вышло — тест завершён автоматически.</p>' : ''}
    <p>Правильных ответов: <b>${correct}</b> из ${questions.length}</p>
    <p>Неправильных: ${wrong}, без ответа: ${skipped}</p>
    <p>Нарушений правил: <b style="color:#ff8f7a">${violations}</b>${violations>0 ? ' (штраф −' + violations*5 + ' баллов)' : ''}</p>
    <p style="font-size:1.4rem; margin-top:10px;">Итоговый балл: <span class="${vc}"><b>${score}</b></span> / 100</p>
    <p style="font-size:1.1rem;">Оценка: <span class="${vc}"><b>${verdict}</b></span></p>
    <hr style="border-color:rgba(255,255,255,0.15); margin:16px 0;">
    <div id="review">${questions.map((q,qi)=>{
      const sel = document.querySelector(`input[name="q${qi}"]:checked`);
      const ok = sel && parseInt(sel.value)===q.correct;
      return `<div class="q" style="margin:10px 0;">
        <div class="num" style="color:${ok?'#7CFC98':'#ff8f7a'}">${qi+1}. ${ok?'✔':'✘'} ${q.text}</div>
        <div style="color:#9fb4c0;font-size:.9rem;">Правильный ответ: <b style="color:#7fd4ff">${q.answers[q.correct]}</b>${sel&&!ok?`<br>Ваш ответ: ${q.answers[parseInt(sel.value)]}`:''}${!sel?'<br>Вы не ответили':''}</div>
      </div>`;
    }).join('')}</div>`;
  window.scrollTo(0,0);
}
</script>
</body>
</html>