Загрузка данных
Карточка студента — выполненное домашнее задание
Ниже — полное решение: HTML, CSS и JavaScript в одном файле. Логика отделена от оформления, состояние хранится в переменной, DOM обновляется после каждого действия.
```html
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Карточка студента</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background: #f0f2f5;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.card {
background: #ffffff;
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
padding: 32px;
width: 100%;
max-width: 420px;
transition: box-shadow 0.2s;
}
.card__title {
font-size: 20px;
font-weight: 600;
color: #1a1a2e;
margin-bottom: 20px;
text-align: center;
}
.card__row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.card__row:last-of-type {
border-bottom: none;
}
.card__label {
color: #6b7280;
font-size: 14px;
}
.card__value {
font-weight: 600;
color: #1a1a2e;
font-size: 16px;
}
.card__score {
font-size: 32px;
font-weight: 700;
color: #2563eb;
text-align: center;
padding: 16px 0 8px;
transition: color 0.2s;
}
.card__status {
text-align: center;
font-size: 15px;
font-weight: 600;
padding: 8px 0 20px;
transition: color 0.2s;
}
/* Классы статуса, которые переключает JavaScript */
.status--excellent { color: #16a34a; }
.status--good { color: #2563eb; }
.status--pass { color: #d97706; }
.status--fail { color: #dc2626; }
.card__buttons {
display: flex;
gap: 10px;
margin-top: 8px;
}
.btn {
flex: 1;
padding: 12px 16px;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s, transform 0.1s, opacity 0.15s;
}
.btn:active {
transform: scale(0.97);
}
.btn:disabled {
opacity: 0.4;
cursor: not-allowed;
transform: none;
}
.btn--plus {
background: #dcfce7;
color: #16a34a;
}
.btn--plus:hover:not(:disabled) {
background: #bbf7d0;
}
.btn--minus {
background: #fee2e2;
color: #dc2626;
}
.btn--minus:hover:not(:disabled) {
background: #fecaca;
}
.btn--reset {
background: #e5e7eb;
color: #374151;
flex: 0 0 100%;
margin-top: 10px;
}
.btn--reset:hover {
background: #d1d5db;
}
/* Границы допустимого диапазона */
.card--min .card__score { color: #dc2626; }
.card--max .card__score { color: #16a34a; }
</style>
</head>
<body>
<div class="card" id="card">
<h1 class="card__title">Карточка студента</h1>
<div class="card__row">
<span class="card__label">Имя</span>
<span class="card__value" id="studentName">Анна</span>
</div>
<div class="card__row">
<span class="card__label">Группа</span>
<span class="card__value" id="studentGroup">ПКС-41</span>
</div>
<div class="card__score" id="scoreDisplay">75</div>
<div class="card__status" id="statusDisplay">Хорошо</div>
<div class="card__buttons">
<button class="btn btn--minus" id="minusButton">−1 балл</button>
<button class="btn btn--plus" id="plusButton">+1 балл</button>
</div>
<button class="btn btn--reset" id="resetButton">Сбросить</button>
</div>
<script>
// ===== 1. Состояние приложения =====
const INITIAL_SCORE = 75;
const MIN_SCORE = 0;
const MAX_SCORE = 100;
let score = INITIAL_SCORE;
// ===== 2. Ссылки на DOM-элементы =====
const card = document.querySelector('#card');
const scoreDisplay = document.querySelector('#scoreDisplay');
const statusDisplay = document.querySelector('#statusDisplay');
const plusButton = document.querySelector('#plusButton');
const minusButton = document.querySelector('#minusButton');
const resetButton = document.querySelector('#resetButton');
// ===== 3. Вспомогательные функции =====
// Определяем текстовый статус по баллу
function getStatus(value) {
if (value >= 90) return { text: 'Отлично', className: 'status--excellent' };
if (value >= 70) return { text: 'Хорошо', className: 'status--good' };
if (value >= 50) return { text: 'Зачтено', className: 'status--pass' };
return { text: 'Нужно повторить', className: 'status--fail' };
}
// Обновляем интерфейс в соответствии с текущим состоянием
function render() {
// Обновляем число
scoreDisplay.textContent = score;
// Обновляем статус
const status = getStatus(score);
statusDisplay.textContent = status.text;
// Переключаем CSS-класс статуса
statusDisplay.className = 'card__status ' + status.className;
// Подсветка границ диапазона
card.classList.toggle('card--min', score === MIN_SCORE);
card.classList.toggle('card--max', score === MAX_SCORE);
// Блокируем кнопки на границах
minusButton.disabled = score <= MIN_SCORE;
plusButton.disabled = score >= MAX_SCORE;
// Кнопка сброса активна, только если значение отличается от исходного
resetButton.disabled = score === INITIAL_SCORE;
}
// Изменяем состояние на заданную величину с ограничением диапазона
function changeScore(delta) {
const next = score + delta;
// Не допускаем выхода за пределы [0, 100]
if (next < MIN_SCORE || next > MAX_SCORE) {
return;
}
score = next;
render();
}
// ===== 4. Обработчики событий =====
plusButton.addEventListener('click', () => changeScore(1));
minusButton.addEventListener('click', () => changeScore(-1));
resetButton.addEventListener('click', () => {
score = INITIAL_SCORE;
render();
});
// ===== 5. Первичная отрисовка =====
render();
</script>
</body>
</html>
```
Как это работает
Схема ровно та, что описана в лекции:
```
состояние (score) → событие (click) → изменение состояния → render() → обновление DOM
```
Ключевые решения:
1. Состояние — одна переменная let score = 75. Все вычисления идут от неё, а не от текста в DOM.
2. Функция render() — единственное место, где DOM приводится в соответствие с состоянием. Её вызывают все обработчики. Это избавляет от рассинхронизации.
3. Функция getStatus() — возвращает и текст, и CSS-класс. Логика диапазонов (≥90, 70–89, 50–69, <50) собрана в одном месте.
4. Ограничение диапазона — changeScore() не применяет изменение, если новый балл выходит за [0, 100]. Дополнительно кнопки блокируются через disabled на границах.
5. Разделение ответственности — CSS отвечает за внешний вид (включая цвета статусов через классы status--*), JavaScript — только за логику и переключение классов.
6. Кнопка «Сбросить» — возвращает исходный балл 75. Она неактивна, пока значение не изменилось.
Что можно проверить
· Начальное состояние: 75 → «Хорошо».
· «+1» до 90 → «Отлично», кнопка «+1» блокируется на 100.
· «−1» до 49 → «Нужно повторить», кнопка «−1» блокируется на 0.
· «Сбросить» возвращает 75 и деактивируется.
· Границы 0 и 100 подсвечиваются цветом через классы card--min / card--max.