Ты делаешь личный кабинет для SaaS. Пользователи хотят тёмную тему ночью. Нужно переключение темы и сохранение выбора.
html или body (например class="dark" на html).index.html
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Task 1 — Theme Toggle</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-50">
<header class="border-b border-slate-200/70 dark:border-slate-800">
<div class="mx-auto flex max-w-3xl items-center justify-between gap-4 px-5 py-4">
<h1 class="text-lg font-semibold">Личный кабинет</h1>
<button
id="themeToggle"
type="button"
aria-pressed="false"
class="inline-flex items-center gap-3 rounded-full border border-slate-200 bg-white px-3 py-2 text-sm shadow-sm hover:bg-slate-50 active:scale-[0.99]
dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800"
>
<span
class="relative h-5 w-9 rounded-full border border-slate-300 bg-slate-100 dark:border-slate-700 dark:bg-slate-800"
aria-hidden="true"
>
<span
id="thumb"
class="absolute left-0.5 top-0.5 h-3.5 w-3.5 rounded-full bg-slate-900 transition-transform dark:bg-slate-50"
></span>
</span>
<span class="opacity-80">Тема</span>
</button>
</div>
</header>
<main class="mx-auto max-w-3xl px-5 py-6">
<p class="text-base">Проверь: тема должна сохраняться после перезагрузки.</p>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
Подсказка: если localStorage пуст — возьми системную тему (prefers-color-scheme).
</p>
</main>
<script src="./script.js"></script>
</body>
</html>
На странице новости есть кнопка 👍 Лайк. Пользователи иногда нажимают два раза подряд (случайно или из-за лага), и лайков становится больше, чем должно быть. Продакт просит сделать так, чтобы:
На странице есть:
По обычному клику:
Если пользователь нажал ещё раз слишком быстро (например в течение 300 мс) — игнорировать этот клик и написать в статус что-то вроде: “Слишком быстро”.
Если лайков стало 20:
disabled<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Task 2 — Like Button</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-slate-100 text-slate-900">
<main class="mx-auto mt-10 max-w-xl rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
<h1 class="text-lg font-semibold">Новость дня</h1>
<p class="mt-1 text-slate-700">Мы выкатили обновление дизайна 🎉</p>
<div class="mt-4 flex items-center gap-3">
<button
id="likeBtn"
type="button"
class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50"
>
👍 Лайк
</button>
<div class="text-sm font-semibold">
Лайки: <span id="likesCount">0</span>
</div>
</div>
<p id="status" class="mt-3 min-h-[1.25rem] text-sm text-slate-600"></p>
</main>
<script src="./script.js"></script>
</body>
</html>
// TODO (логика):
// - Максимум лайков = 20
// - Если с прошлого принятого клика прошло меньше 300мс — не увеличивать и показать статус "Слишком быстро"
// - Если достигли 20 — отключить кнопку и показать статус "Лимит достигнут"
//
// Подсказка: храни время последнего принятого клика в переменной lastClickAt = 0
// и сравнивай Date.now() - lastClickAt
Ты делаешь страницу для создания статьи в блоге.
У статьи есть:
Обычно адрес страницы формируется автоматически из заголовка.
Но иногда пользователь хочет изменить адрес вручную — и в этом случае автоматическое обновление должно остановиться.
Когда пользователь печатает текст в поле «Заголовок статьи»,
этот текст сразу должен отображаться в блоке «Предпросмотр».
Пока пользователь не вводил ничего в поле «Адрес страницы»:
Адрес страницы формируется по следующим правилам:
-;!, ?, ,, .) удаляются.Пример:
Мой первый пост! → мой-первый-пост
Если пользователь начал печатать в поле «Адрес страницы»:
Рядом с полем адреса должно быть понятно, в каком режиме оно сейчас работает:
При нажатии на кнопку «Сбросить адрес»:
index.html<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Task 3 — Article editor</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-slate-950 text-slate-50">
<main class="mx-auto max-w-3xl p-6">
<h1 class="text-xl font-semibold">Создание статьи</h1>
<div class="mt-6 space-y-4">
<label class="block">
<span class="text-sm text-slate-300">Заголовок статьи</span>
<input
id="titleInput"
type="text"
placeholder="Например: Как мы ускорили билд"
class="mt-2 w-full rounded-xl border border-white/15 bg-white/5 px-3 py-3 outline-none"
/>
</label>
<label class="block">
<span class="text-sm text-slate-300">Адрес страницы</span>
<input
id="slugInput"
type="text"
placeholder="будет создан автоматически"
class="mt-2 w-full rounded-xl border border-white/15 bg-white/5 px-3 py-3 outline-none"
/>
<p id="mode" class="mt-2 text-xs text-slate-400">
Режим: авто
</p>
</label>
<button
id="resetBtn"
type="button"
class="rounded-xl border border-white/15 bg-white/10 px-4 py-2 text-sm hover:bg-white/15"
>
Сбросить адрес
</button>
</div>
<section class="mt-6 rounded-2xl border border-white/10 bg-white/5 p-4">
<h2 class="text-sm font-semibold text-slate-200">Предпросмотр</h2>
<p class="mt-2 text-sm">
Заголовок: <span id="titlePreview">—</span>
</p>
<p class="mt-1 text-sm">
Адрес: <code>/posts/<span id="slugPreview">—</span></code>
</p>
</section>
</main>
<script src="./script.js"></script>
</body>
</html>
/task-3-live-preview/script.js// TODO — что нужно реализовать:
//
// 1. Когда пользователь печатает в поле заголовка:
// - обновлять текст в "Предпросмотре"
// - если адрес страницы НЕ меняли вручную — пересоздавать его из заголовка
//
// 2. Когда пользователь печатает в поле адреса:
// - считать, что теперь адрес редактируется вручную
// - больше НЕ менять его автоматически
//
// 3. Кнопка "Сбросить адрес":
// - снова включить автоматический режим
// - пересоздать адрес из текущего заголовка
//
// Подсказка:
// - используй переменную вроде let isManual = false;
// - для адреса сделай простую функцию:
// "Привет мир!" -> "привет-мир"
Ты делаешь страницу регистрации пользователя.
На странице есть форма с тремя полями: email, пароль и подтверждение пароля.
Пользователь открывает страницу и начинает вводить данные.
Важно, чтобы форма вела себя дружелюбно:
Пока пользователь ничего не вводил, ошибок быть не должно.
Когда пользователь начал вводить данные и ушёл из поля:
Если пользователь видит ошибку и начинает исправлять ввод:
Если пользователь меняет пароль:
При нажатии на кнопку «Создать аккаунт»:
Правила проверки данных:
@ и точка после него.Получить из DOM все поля формы, кнопку отправки и блоки для вывода ошибок.
Отслеживать момент, когда пользователь начал работать с каждым полем.
После выхода из поля проверять его значение и при необходимости показывать ошибку.
При вводе в поле пересчитывать ошибку, если она уже была показана.
При изменении пароля перепроверять поле подтверждения пароля.
При отправке формы:
/index.html<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Task 4 — Form validation</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-slate-100 text-slate-900">
<main class="mx-auto mt-10 max-w-xl rounded-2xl border border-slate-200 bg-white p-6 shadow-sm">
<h1 class="text-xl font-semibold">Регистрация</h1>
<form id="form" novalidate class="mt-5 space-y-4">
<div>
<label for="email" class="block text-sm font-medium">Email</label>
<input
id="email"
type="email"
placeholder="name@company.com"
class="mt-2 w-full rounded-xl border border-slate-200 px-3 py-3 outline-none focus:border-slate-300"
/>
<div id="emailError" class="mt-2 min-h-[1.25rem] text-sm text-red-600"></div>
</div>
<div>
<label for="pass" class="block text-sm font-medium">Пароль</label>
<input
id="pass"
type="password"
placeholder="минимум 8 символов"
class="mt-2 w-full rounded-xl border border-slate-200 px-3 py-3 outline-none focus:border-slate-300"
/>
<div id="passError" class="mt-2 min-h-[1.25rem] text-sm text-red-600"></div>
</div>
<div>
<label for="pass2" class="block text-sm font-medium">Подтверждение пароля</label>
<input
id="pass2"
type="password"
placeholder="повтори пароль"
class="mt-2 w-full rounded-xl border border-slate-200 px-3 py-3 outline-none focus:border-slate-300"
/>
<div id="pass2Error" class="mt-2 min-h-[1.25rem] text-sm text-red-600"></div>
</div>
<button
type="submit"
class="w-full rounded-xl bg-slate-900 px-4 py-3 text-sm font-semibold text-white hover:bg-slate-800"
>
Создать аккаунт
</button>
<p id="status" class="min-h-[1.25rem] text-sm text-slate-700"></p>
</form>
</main>
<script src="./script.js"></script>
</body>
</html>
script.js// TODO:
//
// 1. Получить элементы формы, инпуты и блоки ошибок
// 2. Отслеживать, работал ли пользователь с каждым полем
// 3. Проверять поля после выхода из них и при повторном вводе
// 4. При отправке формы:
// - отменять стандартную отправку
// - проверять все поля
// - либо показывать ошибки, либо сообщение об успехе
//
// Подсказка:
// удобно хранить информацию о том, трогал ли пользователь поле,
// в data-атрибуте или в обычном объекте