Загрузка данных
<!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>