Вёрстка по Grid. Часть 2: размещение элементов

Чтение — около 7 минут· без учёта практики

В первой части урока мы задавали колонки сетки, а элементы сами заполняли ячейки по порядку. Этого хватает для галереи, где все карточки равноправны. Но часто элемент должен занять конкретное место: фото — две колонки сразу, шапка — всю ширину страницы, термин — только левую колонку. Для этого в Grid есть свойства, которые задают самим грид-элементам.

Номера линий

Колонки и строки сетки разделены линиями. Линии нумеруются с 1: у сетки из трёх колонок четыре вертикальные линии — слева от первой колонки, между колонками и справа от последней. Нумерация идёт и с конца отрицательными числами: -1 — последняя линия явной сетки, -2 — предпоследняя. Добавленные автоматически неявные колонки и строки не меняют эти отрицательные номера. Номера линий показывает подсветка сетки в инструментах разработчика, о которой шла речь в первой части. Здесь предполагается горизонтальное письмо слева направо: при direction: rtl первая линия колонок находится справа, а не слева.

Свойство grid-column указывает, между какими вертикальными линиями стоит элемент: grid-column: 1 / 3 — от первой линии до третьей, то есть первые две колонки. Свойство grid-row делает то же для горизонтальных линий. Запись grid-column: 1 / -1 растягивает элемент на всю ширину явной сетки при любом числе колонок. Если указать одно число, например grid-column: 2, элемент займёт одну колонку, начиная с этой линии.

Это сокращения: grid-column объединяет свойства grid-column-start и grid-column-end, а grid-row — grid-row-start и grid-row-end.

Номера линий: попробуйте сами

Фото занимает две колонки и две строки, подпись — от третьей линии до последней. Остальные элементы сами заполнили свободные ячейки. Замените у .photo значение grid-row на 1 / 2 — фото станет ниже, а элементы займут освободившиеся ячейки.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Размещение по номерам линий</title>
    <style>
      body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; margin: 1rem; }
      .grid { display: grid; gap: 0.5rem; border: 2px dashed tan; padding: 0.5rem; margin-bottom: 1rem; }
      .grid > * { background-color: #fffaf3; border: 1px solid saddlebrown; border-radius: 8px; padding: 0.5rem; }
      h3 { font-size: 1rem; margin: 0 0 0.25rem; }
      .grid { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 4rem; }
      .photo { grid-column: 1 / 3; grid-row: 1 / 3; }
      .caption { grid-column: 3 / -1; }
    </style>
  </head>
  <body>
    <div class="grid">
      <div class="photo">Фото: grid-column 1 / 3, grid-row 1 / 3</div>
      <div class="caption">grid-column: 3 / -1</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
    </div>
  </body>
</html>

Растягивание на несколько ячеек

Часто важно не где элемент стоит, а сколько ячеек он занимает. Для этого вместо конечной линии пишут ключевое слово span и число: grid-column: span 2 — элемент займёт две колонки там, куда его поставит автоматическое размещение, grid-row: span 2 — две строки.

span: попробуйте сами

Первый элемент шириной в две колонки, второй высотой в две строки. Остальные обтекают их по порядку.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Растягивание на несколько ячеек span</title>
    <style>
      body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; margin: 1rem; }
      .grid { display: grid; gap: 0.5rem; border: 2px dashed tan; padding: 0.5rem; margin-bottom: 1rem; }
      .grid > * { background-color: #fffaf3; border: 1px solid saddlebrown; border-radius: 8px; padding: 0.5rem; }
      h3 { font-size: 1rem; margin: 0 0 0.25rem; }
      .grid { grid-template-columns: repeat(4, 1fr); }
      .wide { grid-column: span 2; }
      .tall { grid-row: span 2; }
    </style>
  </head>
  <body>
    <div class="grid">
      <div class="wide">span 2 колонки</div>
      <div class="tall">span 2 строки</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
      <div>6</div>
    </div>
  </body>
</html>

Именованные области

Номера линий неудобны для макета целой страницы: по ним трудно представить, как всё выглядит. Свойство grid-template-areas позволяет нарисовать макет прямо в CSS. Каждая строка сетки — это строка в кавычках, а слова в ней — имена областей по колонкам. В каждой строке должно быть одинаковое число ячеек. Одно имя в соседних ячейках объединяет их в одну область. Точка . обозначает ячейку без именованной области; автоматическое размещение может поставить туда элемент.

CSS
.page {
  display: grid;
  grid-template-columns: 10rem 1fr;
  grid-template-areas:
    "header header"
    "aside  main"
    "footer footer";
}

Элемент помещают в область свойством grid-area с её именем: header { grid-area: header; }. Область должна быть прямоугольной: буквой «Г» или с разрывом её не нарисовать — такое значение браузер не примет.

Именованные области: попробуйте сами

Шапка и подвал занимают всю ширину, боковая колонка слева от основного содержимого. Поменяйте местами слова aside и main во второй строке шаблона — колонки поменяются местами, а HTML останется прежним.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Именованные области grid-template-areas</title>
    <style>
      body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; margin: 1rem; }
      .grid { display: grid; gap: 0.5rem; border: 2px dashed tan; padding: 0.5rem; margin-bottom: 1rem; }
      .grid > * { background-color: #fffaf3; border: 1px solid saddlebrown; border-radius: 8px; padding: 0.5rem; }
      h3 { font-size: 1rem; margin: 0 0 0.25rem; }
      .page {
        grid-template-columns: 10rem 1fr;
        grid-template-areas:
          "header header"
          "aside  main"
          "footer footer";
      }
      .page header { grid-area: header; }
      .page aside { grid-area: aside; }
      .page main { grid-area: main; }
      .page footer { grid-area: footer; }
    </style>
  </head>
  <body>
    <div class="grid page">
      <header>Шапка: header</header>
      <main>Основное содержимое: main. Подпись к фото Тони и рассказ о прогулке.</main>
      <aside>Боковая колонка: aside</aside>
      <footer>Подвал: footer</footer>
    </div>
  </body>
</html>

Явная и неявная сетка

Колонки и строки, заданные в grid-template-columns, grid-template-rows или grid-template-areas, образуют явную сетку. Если элементов больше, чем её ячеек, или элемент поставлен за её пределы, браузер достраивает неявную сетку: добавляет строки (а при необходимости и колонки).

Размер добавленных строк задаёт свойство grid-auto-rows, колонок — grid-auto-columns. По умолчанию размер равен auto: зависит от содержимого и выравнивания сетки. Удобная запись grid-auto-rows: minmax(5rem, auto): строки не ниже 5rem, но растут, если содержимое не помещается.

Направление автоматического размещения задаёт свойство grid-auto-flow. По умолчанию row: элементы заполняют строку за строкой. При column они заполняют колонку за колонкой, а новые колонки при обычном горизонтальном письме слева направо добавляются справа.

Неявная сетка: попробуйте сами

В явной сетке одна строка высотой 3rem, остальные строки браузер добавил сам. Во второй сетке их высоту задаёт grid-auto-rows. В третьей элементы идут по колонкам: две явные строки, новые колонки шириной 6rem.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Явная и неявная сетка</title>
    <style>
      body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; margin: 1rem; }
      .grid { display: grid; gap: 0.5rem; border: 2px dashed tan; padding: 0.5rem; margin-bottom: 1rem; }
      .grid > * { background-color: #fffaf3; border: 1px solid saddlebrown; border-radius: 8px; padding: 0.5rem; }
      h3 { font-size: 1rem; margin: 0 0 0.25rem; }
      .grid { grid-template-columns: repeat(3, 1fr); grid-template-rows: 3rem; }
      .auto { grid-auto-rows: 5rem; }
      .flow { grid-auto-flow: column; grid-auto-columns: 6rem; grid-template-rows: repeat(2, 3rem); grid-template-columns: none; }
    </style>
  </head>
  <body>
    <h3>явная строка 3rem, остальные — по содержимому</h3>
    <div class="grid"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div></div>
    <h3>grid-auto-rows: 5rem</h3>
    <div class="grid auto"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div></div>
    <h3>grid-auto-flow: column</h3>
    <div class="grid flow"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div></div>
  </body>
</html>

Плотная упаковка

Широкий элемент, которому не хватило места в строке, переходит на следующую, а в строке остаётся пустая ячейка. Значение grid-auto-flow: dense разрешает браузеру заполнять такие пропуски следующими элементами. Но тогда элементы на экране могут стоять не в том порядке, что в HTML. Это подходит только там, где перестановка не меняет смысл: например, для независимых декоративных фотографий. Хронологический альбом, последовательный текст или карточки со ссылками требуют сохранения понятного порядка чтения и фокуса.

dense: попробуйте сами

Без dense третий элемент шириной в две колонки не поместился в первую строку, и в ней осталась пустая ячейка. С dense в неё встал четвёртый элемент.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Плотная упаковка grid-auto-flow: dense</title>
    <style>
      body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; margin: 1rem; }
      .grid { display: grid; gap: 0.5rem; border: 2px dashed tan; padding: 0.5rem; margin-bottom: 1rem; }
      .grid > * { background-color: #fffaf3; border: 1px solid saddlebrown; border-radius: 8px; padding: 0.5rem; }
      h3 { font-size: 1rem; margin: 0 0 0.25rem; }
      .grid { grid-template-columns: repeat(4, 1fr); }
      .wide { grid-column: span 2; }
      .dense { grid-auto-flow: dense; }
    </style>
  </head>
  <body>
    <h3>grid-auto-flow: row</h3>
    <div class="grid"><div>1</div><div class="wide">2 (span 2)</div><div class="wide">3 (span 2)</div><div>4</div><div>5</div></div>
    <h3>grid-auto-flow: dense</h3>
    <div class="grid dense"><div>1</div><div class="wide">2 (span 2)</div><div class="wide">3 (span 2)</div><div>4</div><div>5</div></div>
  </body>
</html>

Выравнивание в ячейках

По умолчанию обычный грид-элемент с автоматическими размерами растянут на всю ячейку. Изображения и элементы с заданным соотношением сторон обычно сохраняют свои пропорции вместо растягивания. Выравнивание в Grid устроено по аналогии с Flexbox, только осей здесь две: строчная (вдоль строки) и блочная (поперёк строк). Они зависят от направления письма; в наших примерах строчная ось горизонтальная, а блочная вертикальная.

  • justify-items — положение всех элементов внутри ячеек вдоль строки: start, end, center, stretch;
  • align-items — то же по блочной оси, в наших примерах по вертикали;
  • place-items — сокращение для обоих: сначала align-items, затем justify-items. place-items: center ставит каждый элемент в центр его ячейки;
  • justify-self и align-self — то же для одного элемента.

Свойства justify-content и align-content в Grid распределяют уже не элементы, а целые колонки и строки, если сетка меньше контейнера.

Выравнивание: попробуйте сами

В первой сетке элементы растянуты на всю ячейку. Во второй place-items: center поставил их в центр ячеек, а первый элемент выровнен отдельно: к началу строки и к низу ячейки.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Выравнивание в ячейках</title>
    <style>
      body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; margin: 1rem; }
      .grid { display: grid; gap: 0.5rem; border: 2px dashed tan; padding: 0.5rem; margin-bottom: 1rem; }
      .grid > * { background-color: #fffaf3; border: 1px solid saddlebrown; border-radius: 8px; padding: 0.5rem; }
      h3 { font-size: 1rem; margin: 0 0 0.25rem; }
      .grid { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 5rem; }
      .center { place-items: center; }
      .center > :first-child { justify-self: start; align-self: end; }
    </style>
  </head>
  <body>
    <h3>по умолчанию: элементы растянуты на всю ячейку</h3>
    <div class="grid"><div>1</div><div>2</div><div>3</div></div>
    <h3>place-items: center, у первого — justify-self: start; align-self: end</h3>
    <div class="grid center"><div>1</div><div>2</div><div>3</div></div>
  </body>
</html>

Grid или Flexbox

Обе модели решают похожие задачи, и часто подходит любая. Помогает простой вопрос: важно ли, чтобы элементы выстраивались в колонки?

  • Flexbox — раскладка в одну линию, где размер определяет содержимое: меню, ряд кнопок, подпись рядом с полем, «таблетки» разной длины. При переносе каждая строка распределяет место сама, поэтому элементы последней строки могут оказаться шире остальных.
  • Grid — раскладка, где размер определяет сетка: галерея одинаковых карточек, каркас страницы, таблица из терминов и описаний. Колонки общие для всех строк.

Модели можно сочетать на одной странице и даже вкладывать друг в друга: каркас страницы на Grid, а меню внутри шапки на Flexbox. На сайте Тони меню и строки формы сделаны на Flexbox, а фотоальбом и список команд — на Grid.

Grid или Flexbox: попробуйте сами

Пять игрушек в контейнерах одинаковой ширины. Во Flexbox два элемента второй строки растянулись шире верхних. В Grid они остались ширины своих колонок.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Grid или Flexbox</title>
    <style>
      body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; margin: 1rem; }
      .grid { display: grid; gap: 0.5rem; border: 2px dashed tan; padding: 0.5rem; margin-bottom: 1rem; }
      .grid > * { background-color: #fffaf3; border: 1px solid saddlebrown; border-radius: 8px; padding: 0.5rem; }
      h3 { font-size: 1rem; margin: 0 0 0.25rem; }
      .flex { display: flex; flex-wrap: wrap; gap: 0.5rem; border: 2px dashed tan; padding: 0.5rem; margin-bottom: 1rem; }
      .flex > * { flex: 1 1 8rem; background-color: #fffaf3; border: 1px solid saddlebrown; border-radius: 8px; padding: 0.5rem; }
      .grid { grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); width: 30rem; }
      .flex { width: 30rem; }
    </style>
  </head>
  <body>
    <h3>Flexbox: flex-wrap и flex: 1 1 8rem</h3>
    <div class="flex"><div>мячик</div><div>канат</div><div>косточка</div><div>белка</div><div>тапок</div></div>
    <h3>Grid: repeat(auto-fit, minmax(8rem, 1fr))</h3>
    <div class="grid"><div>мячик</div><div>канат</div><div>косточка</div><div>белка</div><div>тапок</div></div>
  </body>
</html>

Потренируйтесь

Выберите фото в превью и задайте ему grid-column и grid-row: например, 1 / -1, span 2 или 2 / 4. Свойства justify-self и align-self сдвигают фото внутри его ячейки, а настройки контейнера в группе «Контейнер» меняют саму сетку.

Цвета и отступы элементов превью — только оформление, в код они не входят.

Контейнер .container

Размеры колонок через пробел. Используйте fr, minmax(), repeat() и auto-fill. Справочник: grid-template-columns

Размеры явных строк. Строки сверх списка получают размер из grid-auto-rows. Справочник: grid-template-rows

Размер строк, которые сетка создаёт автоматически. Справочник: grid-auto-rows

Как размещаются элементы без явной позиции. dense заполняет пропуски. Справочник: grid-auto-flow

Расстояние между строками и колонками. Можно указать два значения: по вертикали и по горизонтали. Справочник: gap

Выравнивание элементов внутри ячеек по горизонтали. Справочник: justify-items

Выравнивание элементов внутри ячеек по вертикали. Справочник: align-items

Положение всей сетки по горизонтали, когда колонки уже контейнера. Справочник: justify-content

Положение всей сетки по вертикали, когда строки ниже контейнера. Справочник: align-content

Минимальная высота контейнера. Без свободного места выравнивание по поперечной оси незаметно. Справочник: min-height

Элементы

Выберите элемент в превью или в списке «Настраиваем», чтобы изменить его свойства. Общие настройки всех элементов — пункт «все элементы (.item)».

CSS
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 4rem;
  gap: 8px;
}

.item-1 {
  grid-column: span 2;
}

Именованные области и готовые макеты — в конструкторе Grid

Проверьте себя

Выберите ответ — объяснение появится сразу. Ответ можно менять.

1. Сетка из трёх колонок. Какие колонки займёт элемент с grid-column: 1 / 3?
Показать ответ

Первую и вторую — Числа — номера линий, а не колонок: элемент стоит от первой линии до третьей, то есть занимает две колонки.

2. Как растянуть элемент на всю ширину явной сетки при любом числе её колонок?
Показать ответ

grid-column: 1 / -1 — -1 — последняя линия явной сетки. Неявные колонки за её пределами эта запись не охватывает.

3. Что означает grid-column: span 2?
Показать ответ

Элемент займёт две колонки там, куда его поставит автоматическое размещение — span задаёт число ячеек, а положение браузер выбирает сам.

4. Какой шаблон grid-template-areas браузер не примет?
Показать ответ

"a a" "a b" — Область a получилась буквой «Г», а области должны быть прямоугольными.

5. Как поместить элемент footer в область с именем footer?
Показать ответ

footer { grid-area: footer; } — grid-area с именем помещает элемент в одноимённую область шаблона.

6. В сетке задана одна строка, а элементов больше, чем ячеек. Что сделает браузер?
Показать ответ

Добавит строки неявной сетки; их высоту задаёт grid-auto-rows — По умолчанию высота добавленных строк подстраивается под содержимое.

7. Почему grid-auto-flow: dense не стоит использовать для ссылок и текста?
Показать ответ

Элементы на экране могут встать не в том порядке, что в HTML, а фокус и диктор идут по HTML — Перестановка допустима лишь без смысловой последовательности. Хронологический альбом, текст и ссылки требуют понятного порядка чтения и фокуса.

8. Как поставить каждый элемент сетки в центр его ячейки?
Показать ответ

Задать контейнеру place-items: center — place-items — сокращение для align-items и justify-items.

9. Для какой задачи лучше подходит Grid, чем Flexbox?
Показать ответ

Галерея одинаковых карточек, которые должны стоять ровными колонками — В Grid колонки общие для всех строк, поэтому карточки последней строки не растягиваются шире остальных.

Задание

Ожидаемый результат: на главной команды на немецком выделены жирным и стоят в левой колонке одной ширины, а переводы — в правой колонке на одной линии с командами. Если добавить в список ещё одно описание <dd> подряд, оно встанет во вторую колонку под предыдущим, а не перейдёт в первую.

Выполните задание

css/style.css
CSS
/* Общие стили сайта Тони */

/* Ширина и высота включают внутренние отступы и рамку — у всех элементов и псевдоэлементов */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Шрифт заголовков 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: #2b211b; /* цвет для потомков без собственного значения */
  font-size: 1.0625rem; /* 17px при настройке браузера по умолчанию */
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; /* системный шрифт устройства */
  line-height: 1.6; /* множитель: у каждого элемента интервал от его собственного размера шрифта */
  margin: 0; /* убираем отступ браузера: шапка и подвал во всю ширину окна */
}

/* Поля основного содержимого: текст не прижимается к краям окна */
main {
  padding: 1.5rem 1rem;
  max-width: 68rem; /* на широком экране строки не растягиваются на всё окно */
  margin: 0 auto; /* колонка по центру */
}

/* Изображения и видео не шире колонки, пропорции сохраняются */
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: saddlebrown;
  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;
}

/* Вводный блок главной страницы */
.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: saddlebrown;
  text-decoration: none;
  display: inline-block; /* у строчной ссылки вертикальный padding не раздвигает строку */
  padding: 0.4rem 0.9rem;
  border-radius: 999px; /* «таблетка»: радиус больше половины высоты */
}

/* Ссылки оглавления главной страницы */
.toc a {
  color: #6b5d52; /* тёплый серый: контраст 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: #6b5d52; /* тёплый серый: контраст 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: столько колонок, сколько помещается карточек шириной не меньше 15rem */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 1rem;
  list-style: none;
  padding: 0;
}

.gallery figure {
  margin: 0;
}

.gallery img {
  width: 100%; /* фото заполняет ширину своей ячейки */
}
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&nbsp;кг и люблю гулять в любую погоду. Хозяин говорит: <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>
        <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="Мой канат &quot;Удав&quot;">
          <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>
        <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>
      </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>&copy; 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>
          </p>
          <p>
            <label for="email">Электронная почта</label>
            <input type="email" id="email" name="email" autocomplete="email" placeholder="anna@example.com" required>
          </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">
          </p>
        </fieldset>
        <fieldset>
          <legend>Когда гулять</legend>
          <p>
            <label for="date">Дата</label>
            <input type="date" id="date" name="date" required>
          </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>&copy; 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>
        <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>
          </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>&copy; 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>

Продолжите в VS Code

Внесите те же изменения в css/style.css своего сайта my-site, сохраните файл и откройте главную страницу в браузере. Проверка: команды и переводы стоят в двух колонках. Если результат отличается, нажмите в редакторе «Показать решение» и сравните его со своим кодом.