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


Карточка студента — выполненное домашнее задание

Ниже — полное решение: 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.