Практика по JS

Содержание
Структура

Структура практического задания: «Добавляем интерактивность в CV с JavaScript»

1. Введение (5-7 мин)

  • Цель: Показать, как «оживить» статичное CV.
  • Пример Codepen: Демо готового проекта (CV с темной темой, анимациями, динамическими секциями).
  • Исходные данные участников:
    • У всех есть CV на HTML/CSS (предложить универсальный шаблон для тех, у кого нет).
    • Фокус на модификацию существующего кода, а не создание с нуля.

2. Базовое подключение JS (15 мин)

  • Задача: Научиться связывать JS с HTML.
  • Codepen-демо:
    <!-- Добавляем в конец body -->
    <script>
      console.log("CV загружено!");
      document.body.style.border = "2px solid green"; // Визуальный маркер
    </script>
    
  • Практика:
    1. Добавить тег <script> в свой CV.
    2. Проверить работу через console.log().
    3. Изменить цвет заголовка через JS.

3. Переключатель тем (25 мин)

  • Логика:

    1. Создать кнопку в HTML.
    2. Написать функцию переключения классов.
    3. Добавить CSS-стили для темной темы.
  • Codepen-шаблон:

    <button id="theme-toggle">🌓</button>
    
    .dark-theme {
      background: #333;
      color: white;
    }
    
    const toggleBtn = document.getElementById('theme-toggle');
    toggleBtn.addEventListener('click', () => {
      document.body.classList.toggle('dark-theme');
      // Сохранение темы в localStorage (бонус для продвинутых)
    });
    
  • Задание:

    • Реализовать переключатель.
    • Добавить плавный переход через CSS transition.

4. Динамическое отображение навыков (20 мин)

  • Идея: Генерация списка навыков из массива.
  • Codepen-пример:
    const skills = ['HTML', 'CSS', 'Git', 'Адаптивная верстка'];
    const skillsList = document.getElementById('skills');
    
    skills.forEach(skill => {
      const li = document.createElement('li');
      li.textContent = skill;
      skillsList.appendChild(li);
    });
    
  • Задание:
    • Заменить статичный список в CV на динамический.
    • Добавить кнопку сортировки навыков.

5. Интерактивная секция «Опыт работы» (30 мин)

  • Задача: Показать/скрыть описание по клику.
  • Шаги:
    1. Добавить класс .collapsed с max-height: 0 в CSS.
    2. Написать функцию для переключения видимости.
  • Codepen-фрагмент:
    document.querySelectorAll('.experience-header').forEach(header => {
      header.addEventListener('click', () => {
        header.nextElementSibling.classList.toggle('collapsed');
      });
    });
    
  • Дополнительно: Анимация через CSS transition.

6. Валидация формы контактов (20 мин)

  • Цель: Проверка email перед отправкой.
  • Codepen-демо:
    <form id="contact-form">
      <input type="email" id="email">
      <button type="submit">Отправить</button>
    </form>
    
    document.getElementById('contact-form').addEventListener('submit', (e) => {
      e.preventDefault();
      const email = document.getElementById('email').value;
      if (!email.includes('@')) {
        alert('Некорректный email!');
        return;
      }
      // Отправка данных...
    });
    

7. Дополнительные идеи (10 мин)

  • Для самостоятельной работы:
    • Таймер обратного отсчета до следующего карьерного цели.
    • Drag-and-drop для перестановки секций.
    • Анимация прогресс-бара навыков.

8. Итог и рефлексия (10 мин)

  • Проверка: У всех должен быть рабочий Codepen с модифицированным CV.
  • Советы:
    • Как дебажить код через DevTools.
    • Где искать ошибки (Console > Errors).

Основная цель:
Научиться добавлять интерактивность в статичный сайт (CV) с помощью JavaScript, превратив его в динамичное портфолио.

Что узнают участники:

  • Как связывать JavaScript с HTML/CSS.
  • Как создавать интерактивные элементы (переключатель темы, динамические списки, анимации).
  • Как работать с событиями (клики, отправка форм).
  • Как дебажить код и тестировать изменения в реальном времени.

Результат:
У каждого участника будет CV с рабочими JS-элементами, которые можно добавить в портфолио.

Codepen-демо:
Ссылка на пример CV

Что включено в демо:

  • Переключатель светлой/темной темы.
  • Динамически генерируемый список навыков.
  • Секции опыта работы с раскрывающимся описанием.
  • Валидация формы контактов.

Важно:

  • Участники будут модифицировать свой проект, а не создавать его с нуля.
  • Если у кого-то нет готового CV, можно использовать универсальный шаблон (см. раздел 1.3).

Для участников:

  1. Базовый CV на HTML/CSS.

    • Если своего нет, используйте шаблон:

      <header>
        <h1>Иван Иванов</h1>
        <p>Начинающий веб-разработчик</p>
      </header>
      
      <section id="skills">
        <h2>Навыки</h2>
        <ul class="skills-list">
          <!-- Список будет заполнен через JS -->
          <li>HTML</li>
          <li>CSS</li>
        </ul>
      </section>
      
      <section class="experience">
        <h2>Опыт работы</h2>
        <div class="experience-item">
          <h3 class="experience-header">Стажер в IT-компании</h3>
          <p class="experience-description">
            Участие в разработке фронтенда для внутренних проектов.
          </p>
        </div>
      </section>
      
      <section>
        <h2>Связаться со мной</h2>
        <form id="contact-form">
          <input type="email" id="email" placeholder="Ваш email">
          <button type="submit">Отправить</button>
        </form>
      </section>
      
      <!-- Кнопка переключателя темы (пока не работает) -->
      <button id="theme-toggle" style="position: fixed; top: 20px; right: 20px;">🌓</button>
      
      <!-- JS будет добавлен позже -->
      <script></script>
      
  2. Требования к проекту:

    • Все секции должны быть сверстаны (можно без сложного дизайна).
    • Подключенные шрифты и стили (если есть).

Технические требования:

  • Браузер с DevTools (Chrome, Firefox).
  • Аккаунт на Codepen.io для сохранения работы.
  1. DOM-манипуляции:

    • Изменение классов, стилей и содержимого элементов.
    • Пример: document.querySelector(), classList.toggle().
  2. Работа с событиями:

    • Обработка кликов, отправки форм.
    • Пример: addEventListener('click', callback).
  3. Динамическое создание элементов:

    • Генерация HTML из массивов данных.
    • Пример: document.createElement(), appendChild().
  • Для портфолио: CV с интерактивными элементами выделит вас среди других начинающих разработчиков.
  • Для практики: Все примеры приближены к реальным задачам (например, темы оформления есть у большинства сайтов).
  • Для развития: Вы сможете добавить в CV больше фич (анимации, API, слайдеры).

Пошагово внедрим в CV:

  1. Подключение JavaScript.
  2. Переключатель темы.
  3. Динамический список навыков.
  4. Интерактивные секции опыта.
  5. Валидация формы.

Проверьте, что у вас есть:

  • Готовый CV на HTML/CSS или универсальный шаблон.
  • Открыт Codepen для редактирования.
  • Включена консоль браузера (F12 > Console).

Цель раздела: Научить участников подключать JavaScript к HTML, работать с консолью браузера и выполнять простые манипуляции с DOM.

Ключевые концепции:

  1. Тег <script> — элемент для вставки JS-кода.
    • Размещается в конце <body> (для быстрой загрузки контента).
    • В Codepen можно использовать отдельную JS-панель.
  2. DOM (Document Object Model) — представление HTML-документа в виде дерева объектов.
  3. Консоль разработчика — инструмент для отладки (открыть: F12 → Console).

Шаг 1: Добавление тега <script>

<!-- Вставьте перед закрывающим тегом </body> -->
<script>
  // Ваш код будет здесь
</script>

Шаг 2: Проверка подключения

console.log("🚀 JS подключен!"); 
// Сообщение появится в консоли при загрузке страницы

Шаг 3: Простая манипуляция с DOM

// Изменение стилей через свойство style
document.body.style.backgroundColor = "#f0f0f0"; 

// Поиск элемента по селектору
const header = document.querySelector("h1");
header.style.fontFamily = "Courier New";

Шаблон для практики
Исходный код:

<!-- HTML -->
<h2 id="test-header">Проверка JS</h2>
<button id="demo-button">Нажми меня</button>
// JS
const button = document.getElementById("demo-button");
button.addEventListener("click", () => {
  document.getElementById("test-header").textContent = "JS работает!";
});

Задача: Добавить интерактивность в своё CV.

  1. Изменение цвета заголовка:

    const nameTitle = document.querySelector("h1");
    nameTitle.style.color = "#3498db"; // Синий цвет
    
  2. Динамическое сообщение в консоль:

    console.log("Текущая дата:", new Date().toLocaleDateString());
    
  3. Добавление границы секциям:

    document.querySelectorAll("section").forEach(section => {
      section.style.border = "1px solid #eee";
    });
    
Ошибка Решение
Uncaught TypeError: Cannot read properties of null Убедитесь, что элемент существует в HTML.
Скрипт не работает Проверьте, что <script> расположен после элементов, которыми управляет.
Изменения не отображаются Обновите страницу (Ctrl + F5).
  1. Всегда проверяйте консоль на ошибки (F12 → Console).
  2. Используйте console.log() для вывода промежуточных результатов.
  3. Тестируйте код по частям, а не весь сразу.

Для продвинутых:

  • Добавьте анимацию при клике на кнопку:
    button.addEventListener("click", () => {
      button.style.transform = "scale(0.95)";
      setTimeout(() => { button.style.transform = "scale(1)"; }, 100);
    });
    
  • Сообщение в консоли без ошибок.
  • Визуальные изменения на странице (цвет заголовка, фон).
  • Рабочая кнопка в Codepen-примере.

Цель раздела: Реализовать динамическое переключение тем на сайте, сохраняя выбор пользователя.

Ключевые концепции:

  1. CSS-классы — переключаем стили через добавление/удаление классов.
  2. localStorage — сохраняем выбор темы между сессиями.
  3. События — обрабатываем клики на кнопке.

Почему именно так?

  • Изменение классов эффективнее прямого изменения стилей через JS.
  • localStorage позволяет запоминать выбор пользователя.

Шаг 1: Добавить кнопку в HTML

<!-- В любое место внутри body -->
<button id="theme-toggle" aria-label="Переключить тему">🌓</button>

Шаг 2: Стили для темной темы

/* Добавить в CSS */
.dark-theme {
  background: #2c3e50;
  color: white !important; /* Приоритет над вложенными стилями */
}

.dark-theme section {
  background: #34495e;
}

.dark-theme .skills-list li {
  background: #4a6b8b;
}

/* Плавные переходы */
body {
  transition: background 0.3s, color 0.3s;
}

Шаг 3: JavaScript-логика

const themeToggle = document.getElementById('theme-toggle');
const body = document.body;

// Проверяем сохраненную тему
const savedTheme = localStorage.getItem('theme');
if (savedTheme) body.classList.add(savedTheme);

// Обработчик клика
themeToggle.addEventListener('click', () => {
  body.classList.toggle('dark-theme');
  
  // Сохраняем тему
  const currentTheme = body.classList.contains('dark-theme') ? 'dark-theme' : '';
  localStorage.setItem('theme', currentTheme);
});

Особенности реализации:

  • Кнопка с иконкой луны/солнца (можно заменить на текст).
  • Сохранение темы при перезагрузке страницы.
  • Плавные переходы между темами.

Задача: Интегрировать переключатель в своё CV.

  1. Базовый вариант:

    • Реализовать переключение фона body.
    • Добавить консоль-лог при клике:
      console.log('Текущая тема:', body.classList.contains('dark-theme') ? 'темная' : 'светлая');
      
  2. Продвинутый вариант:

    • Изменить иконку кнопки в зависимости от темы.
      themeToggle.textContent = body.classList.contains('dark-theme') ? '☀️' : '🌙';
      
    • Добавить кастомные свойства CSS (CSS Variables):
      :root {
        --primary-bg: #f9f9f9;
        --text-color: #333;
      }
      
      .dark-theme {
        --primary-bg: #2c3e50;
        --text-color: white;
      }
      
      body {
        background: var(--primary-bg);
        color: var(--text-color);
      }
      
Ошибка Решение
Класс добавляется, но стили не меняются Проверьте !important в CSS или специфичность селекторов.
Тема не сохраняется Убедитесь, что localStorage работает (включите cookies в браузере).
Иконка не обновляется Добавьте логику изменения иконки внутри обработчика клика.
  1. Проверяйте классы через Инструменты разработчика (F12 → Elements).
  2. Используйте localStorage.clear() для сброса темы.
  3. Тестируйте на реальных проектах — некоторые стили могут конфликтовать.
  • Системная тема по умолчанию:
    // Автоопределение темы ОС
    const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
    if (isDarkMode) body.classList.add('dark-theme');
    
  • Градиентные переходы — анимируйте изменение цвета фона.
  • Кнопка меняет тему при клике.
  • Тема сохраняется после перезагрузки.
  • Нет конфликтов стилей в секциях CV.

Цель раздела: Научить генерировать HTML-элементы из массива данных, работать с методами массива и DOM.

  • Динамический контент — позволяет обновлять данные без правки HTML (например, список навыков из базы данных).
  • Методы работы:
    • document.createElement() — создание элементов.
    • appendChild() / innerHTML — добавление в DOM.
    • forEach() — итерация по массиву.

Шаг 1: Подготовка HTML

<!-- Замените статичный список на пустой контейнер -->
<section id="skills">
  <h2>Навыки</h2>
  <ul class="skills-list"></ul> <!-- Контейнер для динамического контента -->
</section>

Шаг 2: Создание массива данных

const skills = [
  'HTML5', 
  'CSS3', 
  'JavaScript', 
  'Git', 
  'Адаптивный дизайн',
  'Figma'
];

Шаг 3: Генерация элементов

const skillsList = document.querySelector('.skills-list');

skills.forEach(skill => {
  const li = document.createElement('li'); // 1. Создаем элемент
  li.textContent = skill;                  // 2. Добавляем текст
  li.classList.add('skill-item');          // 3. Добавляем класс (опционально)
  skillsList.appendChild(li);              // 4. Вставляем в DOM
});

Особенности:

  • Кнопка «Добавить навык» для интерактивности.
  • Сортировка навыков по алфавиту.
// Добавьте этот код в пример:
document.getElementById('add-skill').addEventListener('click', () => {
  const newSkill = prompt('Введите новый навык:');
  if (newSkill) skills.push(newSkill);
  updateSkillsList(); // Перерисовываем список
});

function updateSkillsList() {
  skillsList.innerHTML = ''; // Очищаем контейнер
  skills.sort().forEach(skill => { // Сортируем
    const li = document.createElement('li');
    li.textContent = skill;
    skillsList.appendChild(li);
  });
}

Задача: Реализовать динамический список в своём CV.

  1. Базовый вариант:

    • Заменить статичные навыки на массив.
    • Добавить минимум 8 элементов.
  2. Продвинутый вариант:

    • Реализовать фильтрацию навыков по ключевому слову:
    const filteredSkills = skills.filter(skill => skill.includes('CSS'));
    
    • Добавить кнопку «Сортировать А-Я»/«Сортировать Я-А»:
    let isSortedAsc = true;
    document.getElementById('sort-button').addEventListener('click', () => {
      skills.sort((a, b) => isSortedAsc ? b.localeCompare(a) : a.localeCompare(b));
      isSortedAsc = !isSortedAsc;
      updateSkillsList();
    });
    
Ошибка Решение
Cannot read property 'appendChild' of null Убедитесь, что элемент .skills-list существует в HTML.
Дублирование элементов при обновлении Всегда очищайте контейнер (innerHTML = '') перед перерисовкой.
Кириллица сортируется некорректно Используйте localeCompare(): skills.sort((a, b) => a.localeCompare(b)).
  1. Проверяйте массив через console.log(skills) перед рендерингом.
  2. Используйте debugger; для остановки выполнения кода в нужном месте.
  3. Для сложных списков используйте библиотеки типа Vue.js (но это уже для продвинутых).
  • Прогресс-бар навыков — визуализируйте уровень владения:
    const skillsWithLevel = [
      { name: 'HTML', level: 90 },
      { name: 'CSS', level: 85 }
    ];
    // Генерируем div с width = level + '%'
    
  • Drag-and-Drop — разрешите менять порядок навыков (используйте [HTML5 Drag and Drop API](https://developer.mozilla.org/ru/docs/Web

Цель раздела: Научить создавать раскрывающиеся блоки с описанием опыта, используя события и CSS-анимации.

Ключевые концепции:

  1. Событие click — отслеживание клика на элементе.
  2. CSS-классы — управление видимостью через display/max-height.
  3. Плавные переходы — анимация с помощью transition.

Почему именно так?

  • Изменение классов через JS позволяет отделить логику от стилей.
  • CSS-анимации работают эффективнее JS-анимаций.

Шаг 1: Подготовка HTML

<div class="experience-item">
  <h3 class="experience-header">Frontend Developer (2020-2023)</h3>
  <div class="experience-description">
    <p>Разработка интерфейсов для мобильных приложений.</p>
  </div>
</div>

Шаг 2: Добавить CSS для анимации

/* Стили по умолчанию */
.experience-description {
  max-height: 0;           /* Скрываем контент */
  overflow: hidden;        /* Скрываем выходящий за границы текст */
  transition: max-height 0.3s ease-out; /* Анимация раскрытия */
}

/* Класс для раскрытия */
.experience-description.open {
  max-height: 500px;       /* Максимальная высота (подбирается под контент) */
}

Шаг 3: JavaScript-логика

document.querySelectorAll('.experience-header').forEach(header => {
  header.addEventListener('click', () => {
    const description = header.nextElementSibling;
    description.classList.toggle('open'); // Добавляем/удаляем класс

    // Меняем иконку (опционально)
    header.textContent = description.classList.contains('open') 
      ? '▼ Frontend Developer (2020-2023)' 
      : '▶ Frontend Developer (2020-2023)';
  });
});

Особенности:

  • Плавная анимация раскрытия.
  • Динамическое обновление иконки-индикатора.
  • Поддержка нескольких элементов.

Задача: Добавить интерактивность в секцию опыта своего CV.

  1. Базовый вариант:

    • Реализовать раскрытие/скрытие описания.
    • Добавить консоль-лог при клике:
      console.log('Состояние блока:', description.classList.contains('open'));
      
  2. Продвинутый вариант:

    • Сохранять состояние блоков в localStorage.
      // После перезагрузки страницы
      document.querySelectorAll('.experience-header').forEach(header => {
        const description = header.nextElementSibling;
        const isOpen = localStorage.getItem(header.textContent) === 'true';
        if (isOpen) description.classList.add('open');
      });
      
      // При клике
      localStorage.setItem(header.textContent, description.classList.contains('open'));
      
    • Добавить анимацию вращения иконки:
      .experience-header::before {
        content: '▶';
        display: inline-block;
        transition: transform 0.3s;
      }
      
      .experience-description.open + .experience-header::before {
        transform: rotate(90deg);
      }
      
Ошибка Решение
Анимация не работает Убедитесь, что max-height задан в px, а не %.
Контент выходит за границы Добавьте overflow: hidden к контейнеру.
Несколько блоков открываются одновременно Используйте event.target, чтобы определить конкретный элемент.
  1. Проверяйте классы через Инструменты разработчика (F12 → Elements).
  2. Используйте console.log(event.target), чтобы убедиться, что клик обрабатывается правильно.
  3. Тестируйте анимации на реальном контенте — высота max-height должна быть больше фактической высоты блока.
  • Аккордеон-меню — закрывать предыдущий блок при открытии нового:

    header.addEventListener('click', () => {
      document.querySelectorAll('.experience-description').forEach(desc => {
        if (desc !== description) desc.classList.remove('open');
      });
    });
    
  • Анимация opacity — комбинируйте max-height и opacity для эффекта “появления”.

  • Описание раскрывается/скрывается по клику.
  • Анимация плавная, без рывков.
  • Состояние блоков сохраняется (для продвинутой версии).

Цель раздела: Научить проверять данные формы на стороне клиента, отображать ошибки и предотвращать некорректные действия пользователя.

  • Основная задача:
    • Обеспечить корректность данных (например, email содержит «@»).
    • Улучшить UX через мгновенную обратную связь.
  • Методы валидации:
    • Нативная HTML5-валидация (атрибуты required, type="email").
    • Кастомная валидация через JavaScript.

Шаг 1: Подготовка HTML-формы

<form id="contact-form">
  <input type="text" id="name" placeholder="Имя" required>
  <input type="email" id="email" placeholder="Email" required>
  <button type="submit">Отправить</button>
  <p class="error-message" style="color: red; display: none;"></p>
</form>

Шаг 2: JavaScript-обработчик отправки

document.getElementById('contact-form').addEventListener('submit', (e) => {
  e.preventDefault(); // Отменяем перезагрузку страницы

  const name = document.getElementById('name').value.trim();
  const email = document.getElementById('email').value.trim();
  const errorMessage = document.querySelector('.error-message');

  // Сбрасываем предыдущие ошибки
  errorMessage.style.display = 'none';

  // Проверка имени
  if (name.length < 2) {
    showError('Имя должно содержать минимум 2 символа');
    return;
  }

  // Проверка email
  if (!validateEmail(email)) {
    showError('Некорректный email');
    return;
  }

  // Если все проверки пройдены
  alert('Данные отправлены!');
  e.target.reset();
});

function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); // Регулярное выражение
}

function showError(text) {
  const errorMessage = document.querySelector('.error-message');
  errorMessage.textContent = text;
  errorMessage.style.display = 'block';
}

Рабочий пример
Особенности:

  • Подсветка невалидных полей:
    .invalid {
      border: 2px solid #e74c3c !important;
    }
    
  • Динамическое обновление ошибок.

Задача: Реализовать валидацию для своей формы.

  1. Базовый вариант:

    • Проверка пустых полей.
    • Валидация email на наличие «@» и домена.
  2. Продвинутый вариант:

    • Валидация номера телефона через регулярное выражение:
      function validatePhone(phone) {
        return /^\+?(\d{1,3})?[-. ]?(\(?\d{3}\)?[-. ]?)?[\d-. ]{7,10}$/.test(phone);
      }
      
    • AJAX-отправка данных на моковый API:
      fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        body: JSON.stringify({ name, email })
      });
      
Ошибка Решение
Форма перезагружает страницу Добавьте e.preventDefault().
Регулярное выражение не работает Проверьте его на RegExr.
Ошибки не сбрасываются Добавьте errorMessage.style.display = 'none' перед проверками.
  1. Используйте console.log(email) для вывода введенных данных.
  2. Тестируйте регулярные выражения отдельно (например, в RegExr).
  3. Проверьте, что id элементов совпадают в HTML и JS.
  • Валидация в реальном времени — проверка при вводе:
    document.getElementById('email').addEventListener('input', () => {
      const email = document.getElementById('email').value;
      if (!validateEmail(email)) {
        showError('Некорректный email');
      }
    });
    
  • Кастомные подсказки — всплывающие тултипы вместо alert().
  • Форма не отправляется при ошибках.
  • Сообщения об ошибках четкие и понятные.
  • Поля подсвечиваются при невалидных данных.

Цель раздела: Вдохновить участников на самостоятельное развитие проекта, показав возможности расширения функционала с помощью JavaScript.

Ключевые направления:

  1. Анимации — улучшение UX через визуальные эффекты.
  2. Работа с API — динамическая загрузка данных (погода, курсы валют).
  3. Интерактивность — drag-and-drop, кастомные виджеты.

Почему это важно?

  • Дополнительные фичи делают CV уникальным.
  • Позволяют продемонстрировать навыки работы с разными технологиями.

Задача: Показать, сколько дней осталось до достижения карьерной цели.

<!-- HTML -->
<div class="goal-timer">
  <p>До старта в новой компании: <span id="countdown"></span></p>
</div>
// JS
const targetDate = new Date('2024-12-31');
const timerElement = document.getElementById('countdown');

function updateTimer() {
  const now = new Date();
  const diff = targetDate - now;
  const days = Math.floor(diff / (1000 * 60 * 60 * 24));
  timerElement.textContent = `${days} дней`;
}

updateTimer();
setInterval(updateTimer, 86400000); // Обновлять каждые 24 часа

Задача: Отображать текущую погоду через API.

// Используем OpenWeatherMap API
fetch('https://api.openweathermap.org/data/2.5/weather?q=Moscow&appid=ВАШ_API_КЛЮЧ&units=metric&lang=ru')
  .then(response => response.json())
  .then(data => {
    const temp = data.main.temp;
    document.getElementById('weather').textContent = `🌡️ ${temp}°C, ${data.weather[0].description}`;
  });

Задача: Позволить менять порядок секций CV.

<!-- HTML -->
<section class="draggable" draggable="true">
  <h2>Опыт работы</h2>
  ...
</section>
// JS
document.querySelectorAll('.draggable').forEach(section => {
  section.addEventListener('dragstart', () => {
    section.classList.add('dragging');
  });

  section.addEventListener('dragend', () => {
    section.classList.remove('dragging');
  });
});

// Логика для определения позиции (можно использовать библиотеки)

Задача: Визуализировать уровень владения технологиями.

<!-- HTML -->
<div class="skill">
  <span>HTML</span>
  <div class="progress-bar">
    <div class="progress" data-level="85%"></div>
  </div>
</div>
/* CSS */
.progress-bar {
  width: 200px;
  height: 10px;
  background: #eee;
}

.progress {
  height: 100%;
  background: #3498db;
  transition: width 0.5s;
}
// JS
document.querySelectorAll('.progress').forEach(bar => {
  const level = bar.dataset.level;
  bar.style.width = level; // Анимируем через JS или CSS
});

  1. Таймер обратного отсчета
  2. Виджет погоды
  3. Анимация прогресс-бара

Задача: Выбрать 1-2 идеи и реализовать их в своем CV.

Базовый уровень:

  • Добавить таймер до цели.
  • Реализовать анимацию при скролле (например, появление секций).

Продвинутый уровень:

  • Интегрировать API (погода, GitHub-статистика).
  • Создать кастомный аудиоплеер для раздела «Хобби».
Ошибка Решение
API не возвращает данные Проверьте CORS-политику и API-ключи.
Анимации тормозят Используйте will-change или transform.
Drag-and-Drop не работает Добавьте атрибут draggable="true".
  1. Для анимаций используйте GSAP или Animate.css.
  2. Для работы с API начните с моковых данных через json-server.
  3. Тестируйте интерактивность на мобильных устройствах.
  1. OpenWeatherMap API — погода.
  2. GitHub API — статистика репозиториев.
  3. CSS Tricks — готовые решения для анимаций.

Участники получат готовое портфолио, которое можно:

  1. Разместить на GitHub Pages.
  2. Дополнять новыми разделами (блог, пет-проекты).
  3. Использовать как шаблон для других проектов.