Итоговая работа Чтение — около 7 минут · без учёта практики
Страницы уже готовы. Хочу, чтобы и игрушки, и расписание, и запись на прогулку выглядели как один сайт!
Результат работы
самостоятельно оформленный многостраничный сайт с общим CSS;
адаптивное меню, фотоальбом, таблица и форма;
организованные стили и проверка результата на разных экранах и с клавиатуры.
Мы прошли путь от первого правила до раскладки, состояний и декоративных эффектов. В
предыдущем уроке добавили радиальную
подсветку. Теперь соберите оформление самостоятельно и проверьте, как решения работают вместе. Новых свойств в этой
работе нет: выбирайте изученные приёмы под задачу, а не ради их количества.
Что дано
В редакторе ниже — та же HTML-основа, которой закончился курс: главная страница, фотоальбом игрушек, форма записи и
страница маршрута, которая также открывается во фрейме главной. Ссылки, подписи, таблица, поля и изображения уже есть.
HTML менять не требуется; работайте в css/style.css. В стартовом CSS оставлены только базовые ограничения
размеров и каркас слоёв, поэтому страницы пока выглядят почти без оформления.
index.html — приветствие, оглавление, расписание, маршрут, видео, аудио и цитата;
pages/toys.html — фотоальбом с подписями и значком любимой карточки;
pages/signup.html — форма с подсказками, ошибками и разными состояниями полей;
pages/walks.html — отдельная страница маршрута;
css/style.css — общий файл оформления для всех страниц; изображения, шрифты и медиаресурсы лежат в
assets при скачивании.
Если вы последовательно собирали свой my-site, можно завершить работу в нём: проверьте требования и
организацию файла. Если хотите проверить себя с чистого листа, возьмите стартовый пример ниже. Не нужно заново писать
тексты, разметку формы или искать изображения.
Требования к результату
Цвета, отступы и точное число колонок не обязаны совпадать с решением. Оцениваем читаемость и поведение, а не
совпадение строки CSS. Сложные 3D-эффекты, библиотеки и все виды градиентов не обязательны. Для обычного текста
проверяйте контраст не ниже 4.5:1, для крупного — 3:1; тень текста сама по себе не заменяет достаточный контраст с
фоном.
Порядок работы
Сначала настройте общие цвета, шрифты, межстрочный интервал, ширину содержимого и безопасные размеры изображений.
Проверьте все страницы.
Соберите меню и фотоальбом. Начните с узкого окна, расширяйте его постепенно; точку медиазапроса выбирайте по
моменту, когда содержимому нужна другая раскладка.
Оформите расписание и форму. Проверьте длинный текст, состояния полей и клавиатуру до добавления эффектов.
Добавьте умеренные переходы и декоративное оформление. Сразу предусмотрите вариант reduced-motion и запасные фоны.
Проведите проверку по списку ниже. Исправляйте причину переполнения или конфликта в DevTools; не скрывайте проблему
глобальным overflow-x: hidden и не добавляйте !important наугад.
Если забыли синтаксис, вернитесь к уроку Grid ,
оформлению форм или
организации CSS . Для проверки цвета
подходит инструмент работы с цветами . Сначала попробуйте
сформулировать правило сами, затем сверяйтесь со справочником.
Перенесите весь файл в слои
В уроке об организации CSS мы отложили полный перенос проекта в слои. Теперь сделайте его целиком: базовые
ограничения, палитру и типографику поместите в base, ширину и поля общего контейнера — в
layout, оформление компонентов, состояния, медиазапросы и эффекты — в components. В начале
файла объявите порядок:
CSS Копировать
@layer base , layout , components ;
@layer base {
}
@layer layout {
}
@layer components {
}
Для обычных авторских правил слой components здесь сильнее layout, а тот сильнее
base. Обычное правило вне слоёв сильнее правил в именованных слоях, поэтому частичный перенос может
неожиданно изменить результат. Не оставляйте старые дубликаты в конце файла. После переноса снова проверьте страницы;
внутри каждого слоя продолжает действовать каскад. Порядок важных объявлений устроен иначе — в этой работе они не
нужны.
Как проверить работу
Размеры окна. Откройте каждую из четырёх страниц на 320, 768 и 1280 CSS px, а также на промежуточной ширине.
Страница не должна прокручиваться по горизонтали; локальная прокрутка широкой таблицы допустима. Узкое окно — не
единственная проверка: увеличьте масштаб браузера до 200% и убедитесь, что текст и действия доступны.
Дополнительно при исходной ширине области страницы 1280 CSS px проверьте масштаб 400%:
доступная ширина станет около 320 CSS px. Отдельно увеличьте размер шрифта в настройках браузера —
это проверяет поведение текста и размеров в rem, а не заменяет масштабирование всей страницы.
Длинное содержимое. Временно удлините подпись фотографии и пункт меню в DevTools. Текст должен перенестись,
не выйти из карточки и не наложиться на значок.
Клавиатура. Используйте Tab, Shift+Tab, Enter и пробел. Проследите порядок фокуса в меню и форме, его
видимость и доступность контейнера таблицы. Не убирайте outline без равноценной замены.
Форма. Оставьте обязательные поля пустыми, введите неверный адрес почты и число вне диапазона. Проверьте
текст ошибки и обычное состояние после исправления. Это учебная форма: проверяем оформление и встроенные состояния
браузера; отправка заявки на сервер в работу не входит.
Изображения и шрифты. Проверьте загрузку ресурсов в Network и запасной шрифт при блокировке веб-шрифта.
Отключите фоновую картинку: текст остаётся понятным на однотонном фоне. Подписи фотографий читаются на самых светлых
участках снимков.
Движение и страницы. Включите эмуляцию prefers-reduced-motion: reduce. Приветствие остаётся видимым, движения
нет. Проверьте главную и все страницы по ссылкам, в том числе после изменения общих переменных.
Встроенный редактор помогает проверять CSS и переходы между страницами. Проверку масштаба браузера, загрузки ресурсов,
шрифтов и полной клавиатурной навигации удобнее выполнить в скачанном проекте. Один удачный скриншот не подтверждает
работу всех состояний.
Проверьте себя
Выберите ответ — объяснение появится сразу. Ответ можно менять.
1. Что прежде всего проверять в итоговой работе? Показать ответ Поведение и читаемость всех страниц по требованиям — Совпадение текста CSS и цветов с образцом не требуется.
2. Почему нельзя перенести в слои лишь часть старого файла? Показать ответ Обычные правила вне слоёв могут перебить правила внутри — Нужно учесть весь каскад, перенести правила согласованно и удалить дубликаты.
3. Как поступить с широкой таблицей на узком экране? Показать ответ Дать понятную локальную прокрутку контейнеру таблицы — Таблица может оставаться двумерной; вся страница не должна расширяться вслед за ней.
4. Что должно произойти при prefers-reduced-motion: reduce? Показать ответ Движение отключается, содержимое остаётся доступным — Отключение анимации не должно оставлять приветствие невидимым.
5. Достаточно ли проверить только мышь и ширину 1280px? Показать ответ Нет: нужны другие размеры, клавиатура и состояния — Знакомый вид на одном экране не проверяет доступность и адаптивность.
6. Как проверить запасной шрифт? Показать ответ Заблокировать загрузку веб-шрифта и проверить текст и раскладку — Запасное семейство должно оставлять текст видимым и пригодным для чтения.
← Назад
Далее →
Выполните итоговую работу
Самостоятельное оформление
Дополните css/style.css по требованиям выше. Используйте исходную разметку и ресурсы. Перед просмотром
решения запишите, почему выбрали Flexbox или Grid, как ограничили ширину содержимого и что сделали для узкого экрана
и клавиатуры.
Ожидаемый результат: четыре согласованно оформленные страницы, которые соответствуют проверяемым требованиям. В
решении — вариант знакомого сайта с мягкими тенями, подписями поверх фотографий и тёплой подсветкой. Это образец
одного корректного оформления, а не единственный допустимый CSS.
Стартовый проект Условие задания
css/style.css CSS Копировать
@layer base , layout , components ;
@layer base {
* ,
* ::before ,
* ::after {
box-sizing : border-box ;
}
: root {
}
body {
margin : 0 ;
font-family : system-ui , sans-serif ;
line-height : 1.6 ;
}
img ,
video {
max-width : 100 % ;
height : auto ;
}
progress ,
meter ,
iframe ,
audio ,
input ,
select ,
textarea ,
button {
max-width : 100 % ;
}
fieldset {
min-width : 0 ;
}
}
@layer layout {
main {
}
}
@layer components {
}
index.html HTML Копировать
<!doctype html>
< html lang = "ru" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "description" content = "Сайт карликового пинчера Тони: игрушки, режим дня и запись на прогулку." >
< title > Сайт Тони</ title >
< link rel = "stylesheet" href = "css/style.css" >
</ head >
< body >
< header >
< nav >
< ul >
< li > < a href = "index.html" > Главная</ a > </ li >
< li > < a href = "pages/toys.html" > Мои игрушки</ a > </ li >
< li > < a href = "pages/signup.html" > Запись на прогулку</ a > </ li >
</ ul >
</ nav >
</ header >
< main >
< div class = "intro" >
< h1 id = "top" > Привет! Меня зовут Тони</ h1 >
< p > Я карликовый пинчер. Мне три года, я вешу 8 кг и люблю гулять в любую погоду. Хозяин говорит: < q > Тони — лучший сторож в доме</ q > .</ p >
< p > Мой день рождения — < time datetime = "2023-09-22" > 22 сентября</ time > . < strong > Пожалуйста, не кормите меня со стола!</ strong > </ p >
< p > Моя родословная записана в Российской кинологической федерации (< abbr title = "Российская кинологическая федерация" > РКФ</ abbr > ).</ p >
</ div >
< ul class = "toc" >
< li > < a href = "#toy" > Моя любимая игрушка</ a > </ li >
< li > < a href = "#commands" > Команды, которые я знаю</ a > </ li >
< li > < a href = "#schedule" > Мой режим дня</ a > </ li >
< li > < a href = "#walks" > Где я гуляю</ a > </ li >
< li > < a href = "#song" > Песня для прогулок</ a > </ li >
</ ul >
< section class = "toy" >
< h2 id = "toy" > Моя любимая игрушка</ h2 >
< p data-toy-id = "17" > Это канат с мячиком. Хозяин бросает его, а я приношу обратно — < em > почти</ em > всегда. < a href = "pages/toys.html" > Посмотреть все игрушки Тони</ a > .</ p >
< figure >
< img src = "assets/tony-toy.png" alt = "Тони сидит и держит в зубах канат с красным мячиком" width = "300" height = "400" title = "Мой канат "Удав"" >
< figcaption > Я и мой канат «Удав»</ figcaption >
</ figure >
< p > < a href = "assets/tony-toy.png" download > Скачать фотографию</ a > </ p >
< p > Подробнее о моей породе — в статье < a href = "https://ru.wikipedia.org/wiki/Карликовый_пинчер" target = "_blank" > «Карликовый пинчер» в Википедии</ a > .</ p >
< hr >
< p > Вечером игра закончилась. Канат остался на полу, а я свернулся калачиком в кресле.</ p >
</ section >
< section >
< h2 > Мой стишок</ h2 >
< p >
Я маленький, но смелый,< br >
Гуляю целый день,< br >
А вечером на кресле< br >
Сплю — и мне не лень.
</ p >
</ section >
< section >
< h2 id = "commands" > Команды, которые я знаю</ h2 >
< p > Хозяин научил меня командам на немецком:</ p >
< dl >
< dt lang = "de" > Sitz!</ dt >
< dd > сидеть</ dd >
< dt lang = "de" > Platz!</ dt >
< dd > лежать</ dd >
< dt lang = "de" > Fuß!</ dt >
< dd > рядом</ dd >
</ dl >
< details >
< summary > Почему команды на немецком?</ summary >
< p > Карликовый пинчер — немецкая порода, и хозяину понравилась эта традиция.</ p >
</ details >
</ section >
< section >
< h2 id = "schedule" > Мой режим дня</ h2 >
< div class = "table-wrap" tabindex = "0" role = "region" aria-label = "Режим дня Тони" >
< table >
< caption > Режим дня Тони</ caption >
< thead >
< tr >
< th scope = "col" > Время</ th >
< th scope = "col" > Будни</ th >
< th scope = "col" > Выходные</ th >
</ tr >
</ thead >
< tbody >
< tr >
< th scope = "row" > 07:00</ th >
< td > Прогулка, 20 минут</ td >
< td > Сплю</ td >
</ tr >
< tr >
< th scope = "row" > 08:00</ th >
< td colspan = "2" > Завтрак</ td >
</ tr >
< tr >
< th scope = "row" > 13:00</ th >
< td > Жду хозяина и сплю</ td >
< td > Прогулка в парке, 1 час</ td >
</ tr >
< tr >
< th scope = "row" > 19:00</ th >
< td colspan = "2" > Ужин</ td >
</ tr >
< tr >
< th scope = "row" > 21:00</ th >
< td colspan = "2" > Вечерняя прогулка</ td >
</ tr >
</ tbody >
</ table >
</ div >
</ section >
< section >
< h2 id = "progress" > Мои успехи</ h2 >
< p > Учу команду «Апорт»: пройдено 3 занятия из 5. < progress value = "3" max = "5" > 3 из 5</ progress > </ p >
< p > Прогулка сегодня: 40 минут. < meter value = "40" min = "0" max = "90" low = "30" high = "60" optimum = "75" > 40 из 90 минут</ meter > </ p >
</ section >
< section >
< h2 id = "walks" > Где я гуляю</ h2 >
< p > Я люблю гулять в сквере у дома.</ p >
< p >
< a href = "pages/walks.html" target = "walk" > Маршрут</ a > |
< a href = "assets/tony.png" target = "walk" > Фото с прогулки</ a >
</ p >
< iframe src = "pages/walks.html" name = "walk" title = "Маршрут и фото прогулки Тони" width = "320" height = "420" > </ iframe >
< h3 > Видео с прогулки</ h3 >
< p > Субтитры помогут, если вы смотрите видео без звука.</ p >
< video width = "180" height = "320" poster = "assets/walk.jpg" controls >
< source src = "assets/walk.mp4" type = "video/mp4" >
< track src = "assets/ru.vtt" kind = "subtitles" srclang = "ru" label = "Русский" default >
< p > < a href = "assets/walk.mp4" > Скачать видео</ a > </ p >
</ video >
< h3 > Как я лаю</ h3 >
< p > Так я разговариваю с хозяином — у нас целый диалог!</ p >
< audio controls >
< source src = "assets/bark.mp3" type = "audio/mpeg" >
< p > < a href = "assets/bark.mp3" > Скачать запись</ a > </ p >
</ audio >
</ section >
< section >
< h2 id = "song" > Песня для прогулок</ h2 >
< p > Хозяин включает эту песню, когда мы выходим гулять.</ p >
< iframe width = "480" height = "270" src = "https://www.youtube.com/embed/ojULkWEUsPs?start=17" title = "Baha Men — Who Let the Dogs Out" loading = "lazy" allow = "fullscreen" > </ iframe >
< p > < a href = "https://www.youtube.com/watch?v=ojULkWEUsPs" > Baha Men — Who Let the Dogs Out на YouTube</ a > </ p >
</ section >
< section >
< h2 > Моя любимая книга</ h2 >
< p > Хозяин читал мне вслух рассказ < cite > «Каштанка»</ cite > Антона Чехова. Он начинается так:</ p >
< figure >
< blockquote >
< p > Молодая рыжая собака — помесь таксы с дворняжкой — очень похожая мордой на лисицу, бегала взад и вперёд по тротуару и беспокойно оглядывалась по сторонам.</ p >
</ blockquote >
< figcaption > — Антон Чехов, < cite > «Каштанка»</ cite > </ figcaption >
</ figure >
</ section >
< p > < a href = "#top" > Наверх</ a > </ p >
</ main >
< footer >
< p > < small > © 2026 Тони и его хозяин</ small > </ p >
< address > Написать хозяину Тони: < a href = "mailto:tony@example.com" > tony@example.com</ a > </ address >
</ footer >
</ body >
</ html >
pages/signup.html HTML Копировать
<!doctype html>
< html lang = "ru" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "description" content = "Запишитесь на совместную прогулку с карликовым пинчером Тони." >
< title > Запись на прогулку с Тони</ title >
< link rel = "stylesheet" href = "../css/style.css" >
</ head >
< body >
< header >
< nav >
< ul >
< li > < a href = "../index.html" > Главная</ a > </ li >
< li > < a href = "toys.html" > Мои игрушки</ a > </ li >
< li > < a href = "signup.html" > Запись на прогулку</ a > </ li >
</ ul >
</ nav >
</ header >
< main >
< h1 > Запись на прогулку</ h1 >
< p > Гуляйте вместе с Тони! Оставьте заявку, и хозяин Тони напишет вам, чтобы договориться о встрече.</ p >
< p > < a href = "../index.html#walks" > Где мы гуляем</ a > </ p >
< form >
< fieldset >
< legend > О вас</ legend >
< p >
< label for = "owner" > Ваше имя</ label >
< input type = "text" id = "owner" name = "owner" autocomplete = "name" required aria-describedby = "owner-error" >
< span class = "field-error" id = "owner-error" > Введите ваше имя.</ span >
</ p >
< p >
< label for = "email" > Электронная почта</ label >
< input type = "email" id = "email" name = "email" autocomplete = "email" placeholder = "anna@example.com" required aria-describedby = "email-error" >
< span class = "field-error" id = "email-error" > Введите адрес электронной почты, например anna@example.com.</ span >
</ p >
</ fieldset >
< fieldset >
< legend > Ваша собака</ legend >
< p >
< label for = "breed" > Порода</ label >
< input type = "text" id = "breed" name = "breed" list = "breeds" >
< datalist id = "breeds" >
< option value = "Карликовый пинчер" > </ option >
< option value = "Такса" > </ option >
< option value = "Йоркширский терьер" > </ option >
< option value = "Беспородная" > </ option >
</ datalist >
</ p >
< p >
< label for = "dogs" > Сколько собак придёт</ label >
< input type = "number" id = "dogs" name = "dogs" min = "1" max = "3" value = "1" aria-describedby = "dogs-error" >
< span class = "field-error" id = "dogs-error" > Укажите число собак от 1 до 3.</ span >
</ p >
</ fieldset >
< fieldset >
< legend > Когда гулять</ legend >
< p >
< label for = "date" > Дата</ label >
< input type = "date" id = "date" name = "date" required aria-describedby = "date-error" >
< span class = "field-error" id = "date-error" > Выберите дату прогулки.</ span >
</ p >
< label > < input type = "radio" name = "time" value = "morning" checked > Утром</ label >
< label > < input type = "radio" name = "time" value = "evening" > Вечером</ label >
</ fieldset >
< fieldset >
< legend > Прогулка</ legend >
< p >
< label for = "leader" > Ведёт прогулку</ label >
< input type = "text" id = "leader" name = "leader" value = "Тони" readonly >
</ p >
< p >
< label for = "route" > Маршрут</ label >
< select id = "route" name = "route" >
< optgroup label = "В городе" >
< option value = "square" > Сквер у дома</ option >
< option value = "pond" selected > Пруд с утками</ option >
</ optgroup >
< optgroup label = "За городом" >
< option value = "forest" > Лес</ option >
< option value = "river" disabled > Берег реки — закрыт до весны</ option >
</ optgroup >
</ select >
</ p >
< p >
< label for = "wishes" > Пожелания</ label >
< textarea id = "wishes" name = "wishes" rows = "4" cols = "40" maxlength = "300" placeholder = "Например: моя собака боится велосипедов" > </ textarea >
</ p >
</ fieldset >
< fieldset >
< legend > Что взять с собой</ legend >
< label > < input type = "checkbox" name = "take" value = "ball" > Мячик</ label >
< label > < input type = "checkbox" name = "take" value = "rope" > Канат</ label >
</ fieldset >
< p >
< button type = "submit" > Записаться на прогулку</ button >
< button type = "reset" > Очистить форму</ button >
</ p >
</ form >
</ main >
< footer >
< p > < small > © 2026 Тони и его хозяин</ small > </ p >
< address > Написать хозяину Тони: < a href = "mailto:tony@example.com" > tony@example.com</ a > </ address >
</ footer >
</ body >
</ html >
pages/toys.html HTML Копировать
<!doctype html>
< html lang = "ru" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "description" content = "Любимые игрушки Тони и правила игры в канат." >
< title > Игрушки Тони</ title >
< link rel = "stylesheet" href = "../css/style.css" >
</ head >
< body >
< header >
< nav >
< ul >
< li > < a href = "../index.html" > Главная</ a > </ li >
< li > < a href = "toys.html" > Мои игрушки</ a > </ li >
< li > < a href = "signup.html" > Запись на прогулку</ a > </ li >
</ ul >
</ nav >
</ header >
< main >
< h1 > Мои игрушки</ h1 >
< ul class = "toy-list" >
< li > Канат с мячиком «Удав» — моя любимая игрушка</ li >
< li > Резиновая косточка — для вечеров, когда гулять уже поздно</ li >
< li > Пищащий ёжик — его я прячу < del > под диваном</ del > < ins > в своей лежанке</ ins > </ li >
</ ul >
< img src = "../assets/tony.png" alt = "Тони сидит и смотрит в камеру" width = "300" height = "400" >
< h2 > Как играть со мной в канат</ h2 >
< ol >
< li > Покажите мне канат.</ li >
< li > Бросьте его подальше.</ li >
< li > Подождите, пока я принесу его обратно.</ li >
< li > Похвалите меня — и всё сначала!</ li >
</ ol >
< section >
< h2 id = "album" > Мой фотоальбом</ h2 >
< ul class = "gallery" >
< li >
< figure >
< img src = "../assets/album-puppy.png" alt = "Тони-щенок сидит и смотрит в камеру" width = "300" height = "400" >
< figcaption > Я в детстве</ figcaption >
</ figure >
</ li >
< li >
< figure >
< img src = "../assets/album-squirrel.png" alt = "Тони лежит в обнимку с плюшевой белкой" width = "300" height = "400" >
< figcaption > С плюшевой белкой</ figcaption >
</ figure >
< span class = "badge" > Любимая</ span >
</ li >
< li >
< figure >
< img src = "../assets/album-hoodie.png" alt = "Тони стоит в бежевой толстовке с капюшоном" width = "300" height = "400" >
< figcaption > В худи для холодной погоды</ figcaption >
</ figure >
</ li >
< li >
< figure >
< img src = "../assets/album-harness.png" alt = "Тони в оранжевой шлейке ждёт прогулку" width = "300" height = "400" >
< figcaption > Шлейка надета — идём гулять!</ figcaption >
</ figure >
</ li >
< li >
< figure >
< img src = "../assets/album-laugh.png" alt = "Тони лежит, запрокинув голову, и широко открыл пасть, будто смеётся" width = "300" height = "400" >
< figcaption > Смеюсь над шуткой хозяина</ figcaption >
</ figure >
</ li >
< li >
< figure >
< img src = "../assets/album-sleep.png" alt = "Тони спит, положив голову на лапы" width = "300" height = "319" >
< figcaption > После прогулки</ figcaption >
</ figure >
</ li >
</ ul >
</ section >
< p > < a href = "../index.html#commands" > Какие команды я знаю</ a > </ p >
< p > < a href = "../index.html" > Вернуться на главную</ a > </ p >
</ main >
< footer >
< p > < small > © 2026 Тони и его хозяин</ small > </ p >
< address > Написать хозяину Тони: < a href = "mailto:tony@example.com" > tony@example.com</ a > </ address >
</ footer >
</ body >
</ html >
pages/walks.html HTML Копировать
<!doctype html>
< html lang = "ru" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "description" content = "Маршрут прогулки Тони: сквер, пруд и поляна для игры с канатом." >
< title > Где гуляет Тони</ title >
< link rel = "stylesheet" href = "../css/style.css" >
</ head >
< body >
< h1 > Мой маршрут</ h1 >
< ol >
< li > Сквер у дома.</ li >
< li > Пруд с утками.</ li >
< li > Поляна для игры с канатом.</ li >
</ ol >
</ body >
</ html >
/* Все обычные правила проекта распределены по слоям. */
@layer base, layout, components;
@layer base {
/* Общие стили сайта Тони */
/* Ширина и высота включают внутренние отступы и рамку — у всех элементов и псевдоэлементов */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Палитра сайта в пользовательских свойствах: цвет меняется в одном месте */
:root {
--accent: saddlebrown; /* заголовки, меню, акценты */
--text: #2b211b; /* основной текст */
--text-muted: #6b5d52; /* подписи и второстепенный текст */
}
/* Шрифт заголовков Nunito (лицензия SIL Open Font License): кириллица и латиница в разных файлах */
@font-face {
font-family: "Nunito";
src: url("../assets/nunito-cyrillic-800-normal.woff2") format("woff2");
font-weight: 800;
font-display: swap; /* пока шрифт загружается, текст виден запасным шрифтом */
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "Nunito";
src: url("../assets/nunito-latin-800-normal.woff2") format("woff2");
font-weight: 800;
font-display: swap;
unicode-range: U+0000-00FF, U+2000-206F, U+20AC, U+2122;
}
body {
background-color: linen;
color: var(--text); /* цвет для потомков без собственного значения */
font-size: 1.0625rem; /* 17px при настройке браузера по умолчанию */
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
sans-serif; /* системный шрифт устройства */
line-height: 1.6; /* множитель: у каждого элемента интервал от его собственного размера шрифта */
margin: 0; /* убираем отступ браузера: шапка и подвал во всю ширину окна */
}
/* Поля основного содержимого: текст не прижимается к краям окна */
/* Изображения и видео не шире колонки, пропорции сохраняются */
img,
video {
max-width: 100%;
height: auto;
}
/* Фреймы, аудиоплеер, индикаторы и поля формы не выходят за колонку */
progress,
meter,
iframe,
audio,
input,
select,
textarea,
button {
max-width: 100%;
}
/* Разрешаем группе полей сужаться вместе с колонкой. */
fieldset {
min-width: 0;
}
/* Общий цвет заголовков — групповой селектор */
h1,
h2 {
color: var(--accent);
font-family: "Nunito", "Trebuchet MS", system-ui, sans-serif;
font-weight: 800;
line-height: 1.2; /* крупному тексту хватает меньшего интервала */
}
/* Размеры заголовков в rem — от размера шрифта корня, а не родителя */
h1 {
text-align: center;
font-size: 2.5rem;
}
h2 {
font-size: 1.75rem;
}
}
@layer layout {
main {
padding: 1.5rem 1rem;
max-width: 68rem; /* на широком экране строки не растягиваются на всё окно */
margin: 0 auto; /* колонка по центру */
}
}
@layer components {
/* Вводный блок главной страницы */
.intro {
background-color: hsl(30, 80%, 95%);
padding: 1.5rem; /* фон вокруг текста; заодно не выпускает margin заголовка за пределы блока */
border-radius: 16px; /* скругляется и фон, хотя рамки нет */
/* Лапка в правом нижнем углу; цвет фона выше остаётся запасным, если картинка не загрузится */
background-image: url("../assets/paw.svg");
background-repeat: no-repeat;
background-position: right 1rem bottom 1rem;
background-size: 4rem;
}
/* Разделы главной — светлые карточки на фоне страницы */
main > section {
background-color: #fffaf3;
border: 1px solid hsl(28, 30%, 82%);
border-radius: 16px;
padding: 1.5rem;
margin-top: 1.5rem;
}
/* Ширина вводных абзацев ограничена 60ch; количество букв в строке зависит от шрифта и текста */
.intro p {
max-width: 60ch;
}
/* Важное предупреждение во вводном блоке — темнее основного акцента */
.intro strong {
color: #6e3510;
}
/* Оглавление главной страницы — переносимый ряд «таблеток» на Flexbox */
.toc {
background-color: white;
border: 1px solid tan;
display: flex;
flex-wrap: wrap; /* «таблетки», которым не хватает места, переходят на новую строку */
gap: 0.5rem; /* промежутки между «таблетками» и между строками */
list-style: none;
padding: 0.75rem;
}
/* Подчёркивание ссылок чуть ниже букв: не задевает выносные элементы */
a {
text-underline-offset: 3px;
}
/* Тонкая линия отделяет шапку от страницы */
header {
border-bottom: 1px solid tan;
}
/* Меню на Flexbox: пункты могут переноситься, промежутки задаёт gap */
nav ul {
margin: 0;
padding: 0.5rem 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
list-style: none;
}
/* Ссылка на запись стоит отдельно у правого края: автоматический отступ забирает свободное место */
nav li:last-child {
margin-left: auto;
}
/* Ссылки меню в шапке — селектор потомков */
nav a {
color: var(--accent);
text-decoration: none;
display: inline-block; /* у строчной ссылки вертикальный padding не раздвигает строку */
padding: 0.4rem 0.9rem;
border-radius: 999px; /* «таблетка»: радиус больше половины высоты */
}
/* Ссылки оглавления главной страницы */
.toc a {
color: var(--text-muted); /* тёплый серый: контраст 6.34:1 на белом фоне оглавления */
display: block; /* ссылка заполняет «таблетку» целиком */
padding: 0.25rem 0.75rem;
border: 1px solid tan;
border-radius: 999px;
text-decoration: none;
}
/* Фотографии со скруглёнными углами */
figure img {
border-radius: 8px;
}
figcaption {
color: var(--text-muted); /* тёплый серый: контраст 5.64:1 на фоне linen */
font-size: 0.95rem;
}
footer {
background-color: hsl(30, 50%, 90%);
text-align: center;
padding: 1rem;
}
/* Ссылка в подвале берёт цвет текста подвала */
footer a {
color: inherit;
}
/* Поля формы получают шрифт страницы вместо шрифта браузера */
input,
select,
textarea,
button {
font: inherit;
}
/* Ссылки темнеют при наведении указателя */
a:hover {
color: #5c2d0c;
}
/* Ссылки меню подчёркиваются при наведении и фокусе с клавиатуры */
nav a:hover,
nav a:focus-visible {
text-decoration: underline;
}
/* Заметная рамка фокуса у ссылок, полей и кнопок при работе с клавиатуры */
:focus-visible {
outline: 3px solid #1f6feb;
outline-offset: 3px;
}
/* Цитата из книги — шрифтом с засечками и курсивом */
blockquote {
font-family: Georgia, "Times New Roman", serif;
font-style: italic;
border-left: 4px solid tan; /* черта слева выделяет цитату */
padding-left: 1rem;
}
/* Подпись под цитатой — у правого края */
blockquote + figcaption {
text-align: right;
}
/* Строки формы на Flexbox: подпись — колонка постоянной ширины, поле занимает остаток строки,
а на узком экране переносится под подпись */
fieldset p {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 1rem;
}
fieldset p label {
flex: 0 1 12rem; /* не растёт, но может сжаться в узкой строке */
min-width: 0;
}
fieldset p input,
fieldset p select,
fieldset p textarea {
flex: 1 1 12rem; /* растёт и сжимается от исходной ширины 12rem */
min-width: 0; /* поле может стать уже своего автоматического минимума */
}
/* Фотоальбом на Grid. Все правила альбома собраны во вложенный блок: & — сам .gallery */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
gap: 1rem;
list-style: none;
padding: 0;
/* Карточка — точка отсчёта для значка */
& li {
position: relative;
}
/* Карточка со значком выделена рамкой */
& li:has(.badge) {
outline: 3px solid tan;
outline-offset: 4px;
border-radius: 8px;
}
& figure {
margin: 0;
}
& img {
width: 100%; /* фото заполняет ширину своей ячейки */
}
}
/* Список команд: первая колонка подстраивается под текст, оставляя место для перевода. */
dl {
display: grid;
grid-template-columns: fit-content(calc(50% - 0.5rem)) minmax(0, 1fr);
gap: 0.5rem 1rem;
}
dt {
overflow-wrap: anywhere;
grid-column: 1;
font-weight: bold;
}
dd {
grid-column: 2;
overflow-wrap: anywhere; /* длинный перевод переносится внутри своей колонки */
margin: 0; /* браузерный отступ слева больше не нужен: колонку задаёт сетка */
}
/* Длинные слова и адреса переносятся, а не выходят за край узкого экрана */
body {
overflow-wrap: break-word;
}
/* Таблица режима дня на узком экране прокручивается в своей рамке, а не сжимается до нечитаемых колонок */
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
min-width: 28rem;
}
/* Подпись таблицы прокручивается вместе с ней: прижимаем её к началу, чтобы она была видна сразу */
caption {
text-align: left;
}
/* Широкий экран: в разделе «Моя любимая игрушка» фото справа, текст слева.
На узком экране правило не действует, и раздел остаётся одной колонкой. */
@media (width >= 48rem) {
.toy {
display: grid;
grid-template-columns: minmax(0, 1fr) 300px;
column-gap: 1.5rem;
}
.toy figure {
grid-column: 2;
grid-row: 1 / span 6; /* фото рядом со всеми абзацами раздела */
margin: 0;
}
.toy hr {
width: 100%;
}
}
/* Значок «Любимая» в правом верхнем углу карточки; из потока исключён, поэтому фото не сдвигается */
.badge {
position: absolute;
top: 0.5rem;
right: 0.5rem;
padding: 0.2rem 0.7rem;
border-radius: 999px;
background-color: var(--accent);
color: white;
font-size: 0.85rem;
font-weight: bold;
}
/* Шапка прилипает к верхнему краю окна. Фон нужен, чтобы под шапкой не просвечивал текст страницы */
header {
position: sticky;
top: 0;
z-index: 10; /* выше карточек альбома: они тоже позиционированы и записаны в HTML позже шапки */
background-color: linen;
}
/* Липкая шапка должна быть низкой: пункты меню не сжимаются и не переносятся на вторую строку,
а если им не хватает места, меню прокручивается внутри шапки */
nav ul {
flex-wrap: nowrap;
overflow-x: auto;
}
nav li {
flex-shrink: 0;
}
/* Заголовок, к которому ведёт ссылка-якорь, не прячется под липкой шапкой.
В rem: при увеличении текста растут и шапка, и отступ */
[id] {
scroll-margin-top: 4.5rem;
}
/* Ссылка, которая открывается в новой вкладке, помечена стрелкой — посетитель знает, что страница не сменится */
a[target="_blank"]::after {
content: " ↗";
}
/* Ссылка на электронную почту — с конвертом */
a[href^="mailto:"]::before {
content: "✉ ";
}
/* Первая ссылка меню — «Главная» — подписана лапкой без изменения HTML */
nav li:first-child a::before {
content: "🐾 ";
}
/* Первый абзац вводного блока крупнее остальных. :first-child здесь не подойдёт — первым стоит заголовок h1 */
.intro p:first-of-type {
font-size: 1.2rem;
}
/* Список игрушек: вместо стандартного кружка — косточка */
.toy-list li::marker {
content: "🦴 ";
}
/* Номера шагов в нумерованных списках — в цвет заголовков и жирные; текст пунктов не меняется */
ol li::marker {
color: var(--accent);
font-weight: bold;
}
/* Таблица режима дня: соседние рамки объединяются в одну линию */
table {
border-collapse: collapse;
}
/* Ячейки с воздухом и линией снизу; текст — от левого края и сверху, даже если в соседней ячейке две строки */
th,
td {
padding: 0.6rem 0.8rem;
border-bottom: 1px solid tan;
text-align: left;
vertical-align: top;
}
/* Шапка таблицы — в цвет заголовков сайта */
thead th {
background-color: var(--accent);
color: white;
}
/* Чётные строки тела таблицы — на светлом фоне: глаз не теряет строку */
tbody tr:nth-child(even) {
background-color: hsl(30, 80%, 95%);
}
/* Отдельные рамки без промежутков позволяют скруглить крайние ячейки. Линия только снизу не удваивается. */
table {
border-collapse: separate;
border-spacing: 0;
}
thead th:first-child {
border-top-left-radius: 8px;
}
thead th:last-child {
border-top-right-radius: 8px;
}
/* Форма записи: текстовые поля, список и область текста в едином стиле */
input:not([type="radio"], [type="checkbox"]),
select,
textarea {
padding: 0.5rem 0.75rem;
border: 1px solid #8a7660;
border-radius: 8px;
background-color: white;
}
/* Поле «Ведёт прогулку» менять нельзя — фон показывает это */
input[readonly] {
background-color: hsl(30, 80%, 95%);
}
/* Отмеченные флажки и переключатели — в цвет сайта */
input[type="radio"],
input[type="checkbox"] {
accent-color: var(--accent);
}
/* Неверно заполненное поле — после того как посетитель с ним поработал */
input:user-invalid,
select:user-invalid,
textarea:user-invalid {
border-color: firebrick;
}
/* Группы полей — светлые блоки с тонкой рамкой вместо рельефной рамки браузера */
fieldset {
margin-bottom: 1rem;
border: 1px solid #8a7660;
border-radius: 16px;
}
/* Группа полей, в которой сейчас работает посетитель */
fieldset:focus-within {
border-color: var(--accent);
}
/* Кнопки-«таблетки»: основная залита цветом, второстепенная — только рамка */
button {
padding: 0.7rem 1.4rem;
border: 2px solid var(--accent);
border-radius: 999px;
background-color: var(--accent);
color: white;
font-weight: bold;
cursor: pointer;
}
button[type="reset"] {
background-color: transparent;
color: var(--accent);
}
/* Текст ошибок связан с полями через aria-describedby и не полагается только на цвет рамки. */
.field-error {
display: none;
flex-basis: 100%;
color: firebrick;
font-weight: bold;
}
input:user-invalid + .field-error {
display: block;
}
/* Фото любимой игрушки — как карточка-полароид: слегка повёрнуто вокруг нижнего края, будто прижато к столу */
.toy img {
transform: rotate(-2deg);
transform-origin: 50% 100%;
}
/* Под указателем фото выпрямляется и чуть увеличивается */
.toy img:hover {
transform: rotate(0deg) scale(1.03);
}
/* «Таблетка» оглавления под указателем и в фокусе приподнимается: отдельное свойство translate */
.toc a:hover,
.toc a:focus-visible {
translate: 0 -2px;
}
/* Нажатая кнопка будто вдавливается */
button:active {
transform: translateY(1px) scale(0.98);
}
/* Плавные изменения: переходим конкретные свойства, а не all */
.toy img {
transition: transform 0.4s ease-out; /* фото выпрямляется плавно */
}
.toc a {
transition: translate 0.2s ease-out;
}
button {
transition:
background-color 0.2s,
transform 0.1s;
}
/* Посетитель попросил систему уменьшить движение — изменения происходят сразу, без переходов */
@media (prefers-reduced-motion: reduce) {
.toy img,
.toc a,
button {
transition: none;
}
}
/* Лапка у «Главной» машет, когда на ссылку наводят указатель */
@keyframes wag {
0%,
100% {
rotate: 0deg;
}
25% {
rotate: -18deg;
}
75% {
rotate: 18deg;
}
}
nav li:first-child a::before {
display: inline-block; /* строчный псевдоэлемент не поворачивается */
transform-origin: 50% 80%; /* лапка качается от «запястья» */
}
nav li:first-child a:hover::before,
nav li:first-child a:focus-visible::before {
animation-name: wag;
animation-duration: 0.5s;
animation-timing-function: ease-in-out;
animation-iteration-count: 2; /* помахать дважды и остановиться */
}
/* Приветствие на главной плавно появляется снизу при загрузке страницы */
@keyframes rise {
from {
opacity: 0;
translate: 0 16px;
}
/* to не задан: конечный кадр — обычный вид элемента */
}
.intro > * {
/* both: до старта — первый кадр (элемент скрыт во время задержки), после — последний */
animation: rise 0.6s ease-out both;
}
/* Строки появляются друг за другом */
.intro > :nth-child(2) {
animation-delay: 0.1s;
}
.intro > :nth-child(3) {
animation-delay: 0.2s;
}
.intro > :nth-child(4) {
animation-delay: 0.3s;
}
/* Посетитель попросил уменьшить движение — анимаций нет, всё видно сразу */
@media (prefers-reduced-motion: reduce) {
.intro > *,
nav li:first-child a:hover::before,
nav li:first-child a:focus-visible::before {
animation: none;
}
}
button:hover,
button:focus-visible {
background-color: #6b3513;
}
button[type="reset"]:hover,
button[type="reset"]:focus-visible {
background-color: hsl(30, 70%, 90%);
}
/* Подписи читаются на тёмной подложке и без декоративной тени. */
.gallery figcaption {
padding: 0.5rem;
color: white;
background-color: hsl(25, 50%, 15%);
text-shadow: 0 1px 3px rgb(0 0 0 / 60%);
}
/* Два слоя: чёткая тень у края и широкая мягкая тень под карточкой. */
.gallery li {
background-color: #fffaf3;
border-radius: 12px;
box-shadow: 0 1px 2px rgb(0 0 0 / 8%), 0 8px 24px rgb(0 0 0 / 8%);
}
/* Внутренняя тень дополняет уже знакомое преобразование нажатой кнопки. */
button:active {
box-shadow: inset 0 2px 3px rgb(0 0 0 / 25%);
}
/* Фигура обрезает только своё содержимое, тень родительской карточки остаётся снаружи. */
.gallery figure {
display: grid;
position: relative;
overflow: hidden;
border-radius: 12px;
}
/* Изображение и подпись занимают одну ячейку; длинный текст увеличивает её высоту. */
.gallery figure > img {
grid-area: 1 / 1;
align-self: stretch;
height: 100%;
object-fit: cover;
}
.gallery figcaption {
grid-area: 1 / 1;
align-self: end;
z-index: 1;
padding: 2.5rem 0.5rem 0.5rem;
}
/* Запасная однотонная подложка остаётся в прежнем правиле; градиент подключается условно. */
@supports (background-image: linear-gradient(black, white)) {
.gallery figcaption {
background-color: transparent;
background-image: linear-gradient(
to top,
rgb(57 35 19 / 95%) 0,
rgb(57 35 19 / 95%) calc(100% - 2rem),
rgb(57 35 19 / 0%) 100%
);
}
}
/* Лапа остаётся верхним слоем; подсветка — под ней, над однотонным фоном. */
@supports (background-image: radial-gradient(white, transparent)) {
.intro {
background-image:
url("../assets/paw.svg"),
radial-gradient(ellipse at 80% 20%, rgb(255 196 110 / 45%), rgb(255 196 110 / 0%) 65%);
background-size: 4rem, auto;
background-position: right 1rem bottom 1rem, center;
background-repeat: no-repeat;
}
}
}
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Сайт карликового пинчера Тони: игрушки, режим дня и запись на прогулку.">
<title>Сайт Тони</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<!-- Меню сайта -->
<nav>
<ul>
<li><a href="index.html">Главная</a></li>
<li><a href="pages/toys.html">Мои игрушки</a></li>
<li><a href="pages/signup.html">Запись на прогулку</a></li>
</ul>
</nav>
</header>
<main>
<div class="intro">
<h1 id="top">Привет! Меня зовут Тони</h1>
<p>Я карликовый пинчер. Мне три года, я вешу 8 кг и люблю гулять в любую погоду. Хозяин говорит: <q>Тони — лучший сторож в доме</q>.</p>
<p>Мой день рождения — <time datetime="2023-09-22">22 сентября</time>. <strong>Пожалуйста, не кормите меня со стола!</strong></p>
<p>Моя родословная записана в Российской кинологической федерации (<abbr title="Российская кинологическая федерация">РКФ</abbr>).</p>
</div>
<ul class="toc">
<li><a href="#toy">Моя любимая игрушка</a></li>
<li><a href="#commands">Команды, которые я знаю</a></li>
<li><a href="#schedule">Мой режим дня</a></li>
<li><a href="#walks">Где я гуляю</a></li>
<li><a href="#song">Песня для прогулок</a></li>
</ul>
<section class="toy">
<h2 id="toy">Моя любимая игрушка</h2>
<p data-toy-id="17">Это канат с мячиком. Хозяин бросает его, а я приношу обратно — <em>почти</em> всегда. <a href="pages/toys.html">Посмотреть все игрушки Тони</a>.</p>
<figure>
<img src="assets/tony-toy.png" alt="Тони сидит и держит в зубах канат с красным мячиком" width="300" height="400" title="Мой канат "Удав"">
<figcaption>Я и мой канат «Удав»</figcaption>
</figure>
<p><a href="assets/tony-toy.png" download>Скачать фотографию</a></p>
<p>Подробнее о моей породе — в статье <a href="https://ru.wikipedia.org/wiki/Карликовый_пинчер" target="_blank">«Карликовый пинчер» в Википедии</a>.</p>
<hr>
<p>Вечером игра закончилась. Канат остался на полу, а я свернулся калачиком в кресле.</p>
</section>
<section>
<h2>Мой стишок</h2>
<p>
Я маленький, но смелый,<br>
Гуляю целый день,<br>
А вечером на кресле<br>
Сплю — и мне не лень.
</p>
</section>
<section>
<h2 id="commands">Команды, которые я знаю</h2>
<p>Хозяин научил меня командам на немецком:</p>
<dl>
<dt lang="de">Sitz!</dt>
<dd>сидеть</dd>
<dt lang="de">Platz!</dt>
<dd>лежать</dd>
<dt lang="de">Fuß!</dt>
<dd>рядом</dd>
</dl>
<details>
<summary>Почему команды на немецком?</summary>
<p>Карликовый пинчер — немецкая порода, и хозяину понравилась эта традиция.</p>
</details>
</section>
<section>
<h2 id="schedule">Мой режим дня</h2>
<div class="table-wrap" tabindex="0" role="region" aria-label="Режим дня Тони">
<table>
<caption>Режим дня Тони</caption>
<thead>
<tr>
<th scope="col">Время</th>
<th scope="col">Будни</th>
<th scope="col">Выходные</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">07:00</th>
<td>Прогулка, 20 минут</td>
<td>Сплю</td>
</tr>
<tr>
<th scope="row">08:00</th>
<td colspan="2">Завтрак</td>
</tr>
<tr>
<th scope="row">13:00</th>
<td>Жду хозяина и сплю</td>
<td>Прогулка в парке, 1 час</td>
</tr>
<tr>
<th scope="row">19:00</th>
<td colspan="2">Ужин</td>
</tr>
<tr>
<th scope="row">21:00</th>
<td colspan="2">Вечерняя прогулка</td>
</tr>
</tbody>
</table>
</div>
</section>
<section>
<h2 id="progress">Мои успехи</h2>
<p>Учу команду «Апорт»: пройдено 3 занятия из 5. <progress value="3" max="5">3 из 5</progress></p>
<p>Прогулка сегодня: 40 минут. <meter value="40" min="0" max="90" low="30" high="60" optimum="75">40 из 90 минут</meter></p>
</section>
<section>
<h2 id="walks">Где я гуляю</h2>
<p>Я люблю гулять в сквере у дома.</p>
<p>
<a href="pages/walks.html" target="walk">Маршрут</a> |
<a href="assets/tony.png" target="walk">Фото с прогулки</a>
</p>
<iframe src="pages/walks.html" name="walk" title="Маршрут и фото прогулки Тони" width="320" height="420"></iframe>
<h3>Видео с прогулки</h3>
<p>Субтитры помогут, если вы смотрите видео без звука.</p>
<video width="180" height="320" poster="assets/walk.jpg" controls>
<source src="assets/walk.mp4" type="video/mp4">
<track src="assets/ru.vtt" kind="subtitles" srclang="ru" label="Русский" default>
<p><a href="assets/walk.mp4">Скачать видео</a></p>
</video>
<h3>Как я лаю</h3>
<p>Так я разговариваю с хозяином — у нас целый диалог!</p>
<audio controls>
<source src="assets/bark.mp3" type="audio/mpeg">
<p><a href="assets/bark.mp3">Скачать запись</a></p>
</audio>
</section>
<section>
<h2 id="song">Песня для прогулок</h2>
<p>Хозяин включает эту песню, когда мы выходим гулять.</p>
<iframe width="480" height="270" src="https://www.youtube.com/embed/ojULkWEUsPs?start=17" title="Baha Men — Who Let the Dogs Out" loading="lazy" allow="fullscreen"></iframe>
<p><a href="https://www.youtube.com/watch?v=ojULkWEUsPs">Baha Men — Who Let the Dogs Out на YouTube</a></p>
</section>
<section>
<h2>Моя любимая книга</h2>
<p>Хозяин читал мне вслух рассказ <cite>«Каштанка»</cite> Антона Чехова. Он начинается так:</p>
<figure>
<blockquote>
<p>Молодая рыжая собака — помесь таксы с дворняжкой — очень похожая мордой на лисицу, бегала взад и вперёд по тротуару и беспокойно оглядывалась по сторонам.</p>
</blockquote>
<figcaption>— Антон Чехов, <cite>«Каштанка»</cite></figcaption>
</figure>
</section>
<p><a href="#top">Наверх</a></p>
</main>
<footer>
<p><small>© 2026 Тони и его хозяин</small></p>
<address>Написать хозяину Тони: <a href="mailto:tony@example.com">tony@example.com</a></address>
</footer>
</body>
</html>
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Запишитесь на совместную прогулку с карликовым пинчером Тони.">
<title>Запись на прогулку с Тони</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<header>
<nav>
<ul>
<li><a href="../index.html">Главная</a></li>
<li><a href="toys.html">Мои игрушки</a></li>
<li><a href="signup.html">Запись на прогулку</a></li>
</ul>
</nav>
</header>
<main>
<h1>Запись на прогулку</h1>
<p>Гуляйте вместе с Тони! Оставьте заявку, и хозяин Тони напишет вам, чтобы договориться о встрече.</p>
<p><a href="../index.html#walks">Где мы гуляем</a></p>
<form>
<fieldset>
<legend>О вас</legend>
<p>
<label for="owner">Ваше имя</label>
<input type="text" id="owner" name="owner" autocomplete="name" required aria-describedby="owner-error">
<span class="field-error" id="owner-error">Введите ваше имя.</span>
</p>
<p>
<label for="email">Электронная почта</label>
<input type="email" id="email" name="email" autocomplete="email" placeholder="anna@example.com" required aria-describedby="email-error">
<span class="field-error" id="email-error">Введите адрес электронной почты, например anna@example.com.</span>
</p>
</fieldset>
<fieldset>
<legend>Ваша собака</legend>
<p>
<label for="breed">Порода</label>
<input type="text" id="breed" name="breed" list="breeds">
<datalist id="breeds">
<option value="Карликовый пинчер"></option>
<option value="Такса"></option>
<option value="Йоркширский терьер"></option>
<option value="Беспородная"></option>
</datalist>
</p>
<p>
<label for="dogs">Сколько собак придёт</label>
<input type="number" id="dogs" name="dogs" min="1" max="3" value="1" aria-describedby="dogs-error">
<span class="field-error" id="dogs-error">Укажите число собак от 1 до 3.</span>
</p>
</fieldset>
<fieldset>
<legend>Когда гулять</legend>
<p>
<label for="date">Дата</label>
<input type="date" id="date" name="date" required aria-describedby="date-error">
<span class="field-error" id="date-error">Выберите дату прогулки.</span>
</p>
<label><input type="radio" name="time" value="morning" checked> Утром</label>
<label><input type="radio" name="time" value="evening"> Вечером</label>
</fieldset>
<fieldset>
<legend>Прогулка</legend>
<p>
<label for="leader">Ведёт прогулку</label>
<input type="text" id="leader" name="leader" value="Тони" readonly>
</p>
<p>
<label for="route">Маршрут</label>
<select id="route" name="route">
<optgroup label="В городе">
<option value="square">Сквер у дома</option>
<option value="pond" selected>Пруд с утками</option>
</optgroup>
<optgroup label="За городом">
<option value="forest">Лес</option>
<option value="river" disabled>Берег реки — закрыт до весны</option>
</optgroup>
</select>
</p>
<p>
<label for="wishes">Пожелания</label>
<textarea id="wishes" name="wishes" rows="4" cols="40" maxlength="300" placeholder="Например: моя собака боится велосипедов"></textarea>
</p>
</fieldset>
<fieldset>
<legend>Что взять с собой</legend>
<label><input type="checkbox" name="take" value="ball"> Мячик</label>
<label><input type="checkbox" name="take" value="rope"> Канат</label>
</fieldset>
<p>
<button type="submit">Записаться на прогулку</button>
<button type="reset">Очистить форму</button>
</p>
</form>
</main>
<footer>
<p><small>© 2026 Тони и его хозяин</small></p>
<address>Написать хозяину Тони: <a href="mailto:tony@example.com">tony@example.com</a></address>
</footer>
</body>
</html>
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Любимые игрушки Тони и правила игры в канат.">
<title>Игрушки Тони</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<header>
<nav>
<ul>
<li><a href="../index.html">Главная</a></li>
<li><a href="toys.html">Мои игрушки</a></li>
<li><a href="signup.html">Запись на прогулку</a></li>
</ul>
</nav>
</header>
<main>
<h1>Мои игрушки</h1>
<ul class="toy-list">
<li>Канат с мячиком «Удав» — моя любимая игрушка</li>
<li>Резиновая косточка — для вечеров, когда гулять уже поздно</li>
<li>Пищащий ёжик — его я прячу <del>под диваном</del> <ins>в своей лежанке</ins></li>
</ul>
<img src="../assets/tony.png" alt="Тони сидит и смотрит в камеру" width="300" height="400">
<h2>Как играть со мной в канат</h2>
<ol>
<li>Покажите мне канат.</li>
<li>Бросьте его подальше.</li>
<li>Подождите, пока я принесу его обратно.</li>
<li>Похвалите меня — и всё сначала!</li>
</ol>
<section>
<h2 id="album">Мой фотоальбом</h2>
<ul class="gallery">
<li>
<figure>
<img src="../assets/album-puppy.png" alt="Тони-щенок сидит и смотрит в камеру" width="300" height="400">
<figcaption>Я в детстве</figcaption>
</figure>
</li>
<li>
<figure>
<img src="../assets/album-squirrel.png" alt="Тони лежит в обнимку с плюшевой белкой" width="300" height="400">
<figcaption>С плюшевой белкой</figcaption>
</figure>
<span class="badge">Любимая</span>
</li>
<li>
<figure>
<img src="../assets/album-hoodie.png" alt="Тони стоит в бежевой толстовке с капюшоном" width="300" height="400">
<figcaption>В худи для холодной погоды</figcaption>
</figure>
</li>
<li>
<figure>
<img src="../assets/album-harness.png" alt="Тони в оранжевой шлейке ждёт прогулку" width="300" height="400">
<figcaption>Шлейка надета — идём гулять!</figcaption>
</figure>
</li>
<li>
<figure>
<img src="../assets/album-laugh.png" alt="Тони лежит, запрокинув голову, и широко открыл пасть, будто смеётся" width="300" height="400">
<figcaption>Смеюсь над шуткой хозяина</figcaption>
</figure>
</li>
<li>
<figure>
<img src="../assets/album-sleep.png" alt="Тони спит, положив голову на лапы" width="300" height="319">
<figcaption>После прогулки</figcaption>
</figure>
</li>
</ul>
</section>
<p><a href="../index.html#commands">Какие команды я знаю</a></p>
<p><a href="../index.html">Вернуться на главную</a></p>
</main>
<footer>
<p><small>© 2026 Тони и его хозяин</small></p>
<address>Написать хозяину Тони: <a href="mailto:tony@example.com">tony@example.com</a></address>
</footer>
</body>
</html>
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Маршрут прогулки Тони: сквер, пруд и поляна для игры с канатом.">
<title>Где гуляет Тони</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<h1>Мой маршрут</h1>
<ol>
<li>Сквер у дома.</li>
<li>Пруд с утками.</li>
<li>Поляна для игры с канатом.</li>
</ol>
</body>
</html>
Перезагрузить страницу
Тони готовит пример…
Сохраните и проверьте в VS Code
Скачайте ZIP из редактора и распакуйте его в отдельную папку, например my-site-final, чтобы сохранить
предыдущую работу. Откройте проект в VS Code, затем index.html через локальный сервер. Если ещё не запускали его, воспользуйтесь
инструкцией из учебника HTML . Проверьте все четыре страницы по списку выше. Кнопка скачивания сохраняет текущие файлы; для
архива решения сначала измените CSS, затем выберите «Показать решение» и скачайте ZIP. Сравните причины отличий со
своим вариантом, после чего вернитесь к своим файлам и исправьте найденное.
Когда работа готова
Подготовьте папку проекта с четырьмя страницами, общим CSS и ресурсами. Добавьте короткий отчёт: какие ширины и
страницы проверили, как прошла работа с клавиатуры и reduced-motion, что исправили и какие ограничения остались.
Отдельно объясните выбор раскладки, поведение слоёв и назначение пользовательских свойств. Не выдавайте непроверенный
сценарий за пройденный.
Работа готова, когда требования соблюдаются на всех страницах, а вы можете объяснить свои решения и найти правило,
которое определяет вид элемента. Если отдельный пункт пока не получается, вернитесь к соответствующему уроку и
проверьте его на небольшом примере. Финальная цель — самостоятельно управлять оформлением и проверять результат.
Предыдущий урок CSS функции: радиальные градиенты