MBL
Frontend / ЛР2: CSS с нуля — гайд по сайту Ceramic Soul для защиты
Frontend вводный

ЛР2: CSS с нуля — гайд по сайту Ceramic Soul для защиты

cssbemflexboxgriditmo

Гайд к ЛР2 «Оформление документа средствами CSS»: тот же сайт Ceramic Soul из ЛР1, но теперь с внешним видом по макету Figma. Всё объяснено максимально просто, сверху вниз: сначала базовые слова, потом наш проект по файлам, в конце — вопросы, которые могут задать на защите.

1. Что такое CSS и зачем он нужен

HTML — скелет, CSS — одежда. HTML говорит, что это за элемент: заголовок, картинка, ссылка. CSS говорит, как он выглядит: цвет, размер, отступы, где стоит. Они лежат в разных файлах, поэтому внешний вид можно менять, не трогая разметку.

Подключается CSS одной строкой в <head>:

<link rel="stylesheet" href="css/base.css">

Браузер читает HTML, видит эту строку, скачивает файл и применяет правила к элементам.

2. Из каких файлов состоит наш CSS

Каждый файл — один логический блок страницы. Так проще искать и защищать: «за шапку отвечает header.css».

css/
├── base.css          переменные, сброс, шрифт, focus-visible  (все страницы)
├── header.css        шапка: логотип, меню, иконки              (все страницы)
├── footer.css        тёмный подвал                             (все страницы)
├── button.css        кнопка-рамка SHOP NOW / READ / SEND       (главная, блог, about)
├── section.css       секция: заголовок по центру + содержимое  (все страницы)
├── hero.css          первый экран главной
├── showcase.css      CREATE OR BUY: две карточки рядом
├── gallery.css       CHECK OUT OUR WORKS: три картинки, стрелки, точки
├── tabs.css          вкладки FOR TEA / FOR KITCHEN / FOR PLANTS
├── product-card.css  карточка товара в каталоге
├── post-card.css     карточка статьи в блоге
├── split.css         about: картинка + текст / картинка + форма
├── form.css          форма обратной связи
└── map.css           картинка-карта

Правило подключения: страница подключает base.css первым, потом только те файлы, которые ей нужны.

3. Как выглядит одно правило

.header {
    padding: 1.5rem 1rem;
}

Читается так: кто — что менять — на сколько.

  • .header — селектор: к кому применить. Точка значит «класс, имя которого я придумал».
  • padding — свойство: что менять (внутренний отступ).
  • 1.5rem 1rem — значение: на сколько (сверху-снизу и слева-справа).

Пара «свойство: значение;» называется декларацией. Все декларации стоят внутри фигурных скобок.

4. Селекторы: как выбрать нужный элемент

  • p — все элементы <p>.
  • .card — все элементы с class="card".
  • a:hover — ссылка, на которую навели мышь.
  • a:focus-visible — элемент, до которого дошли клавишей Tab.
  • * — вообще всё.
  • h1, h2, h3 — перечисление: и то, и другое, и третье.
  • .tabs__button--active — элемент с таким классом (у нас так подчёркнута выбранная вкладка).
  • .gallery__arrow:first-child — первый ребёнок внутри родителя (так мы отличаем левую стрелку от правой).
  • .header__nav-link[aria-current="page"] — элемент с таким атрибутом (так подсвечена текущая страница).

Селекторы по #id в ЛР2 запрещены, поэтому у нас только классы.

5. Каскад и наследование

Каскад решает спор, если два правила красят один элемент одним свойством:

  1. Побеждает более конкретный селектор (класс сильнее тега).
  2. При равенстве побеждает то правило, что написано ниже.

Отсюда буква C в CSS — Cascading, «каскадный».

Наследование: некоторые свойства (color, font-family, font-size, line-height) сами передаются детям от родителя. Поэтому шрифт мы задали один раз на body, и он работает на всей странице.

Слово !important ломает эту систему, поэтому в ЛР2 оно запрещено — и у нас его нет.

6. Блочная модель

Каждый элемент на странице — прямоугольник из четырёх слоёв, изнутри наружу:

┌──────────────── margin (снаружи, между соседями) ───────────────┐
│  ┌──────────── border (рамка) ────────────────────────────┐     │
│  │  ┌──────── padding (воздух внутри рамки) ───────────┐  │     │
│  │  │        content (текст или картинка)              │  │     │
│  │  └──────────────────────────────────────────────────┘  │     │
│  └────────────────────────────────────────────────────────┘     │
└─────────────────────────────────────────────────────────────────┘
  • content — сам текст или картинка.
  • padding — воздух между содержимым и рамкой (как вата в посылке).
  • border — рамка.
  • margin — отступ снаружи, расстояние до соседей.

Зачем box-sizing: border-box. По умолчанию width — это ширина только содержимого, а padding и рамка прибавляются сверху. Блок шириной 300px с padding 20px получается 340px, и вёрстка ломается. Строка в base.css делает так, что width включает всё:

*,
*::before,
*::after {
    box-sizing: border-box;
}

7. Единицы измерения: почему rem, а не px

  • px — пиксель, размер фиксирован. Для шрифтов в ЛР2 запрещён.
  • rem — «во сколько раз больше корневого размера шрифта». Обычно 1rem = 16px, значит 0.5rem = 8px, 2rem = 32px.
  • % — процент от размера родителя.
  • Число без единицы, например line-height: 1.4, — «1.4 от размера шрифта».

Почему rem. Если человек увеличил шрифт в настройках браузера (плохое зрение), всё в rem вырастет пропорционально, а в px не изменится. Для доступности это важно. Отступы у нас тоже в rem.

8. Переменные в :root

:root {
    --color-accent: #c69978;
    --space-s: 1rem;
}

.brand-name {
    color: var(--color-accent);
}
  • :root — это сам элемент <html>, самый верхний. Что объявлено там, видно везде.
  • Имя переменной начинается с --, использование — через var(...).

Зачем. Цвет пишется один раз, а используется сколько угодно. Захотел поменять акцентный цвет — меняешь одну строку, и он меняется на всём сайте. Имена у нас по роли (--color-accent, --color-surface-dark), а не по виду (--color-brown): если акцент станет синим, имя не соврёт.

В нашем :root: цвета (--color-bg, --color-text, --color-accent, --color-surface-dark, --color-text-on-dark, --color-border), шкала отступов (--space-xs … --space-xl) и типографика (--font-family-base, --font-size-base, --line-height-base).

9. БЭМ: методология именования

ЛР2 требует выбрать одну методологию и объяснить выбор на защите. Мы выбрали БЭМ (Блок–Элемент–Модификатор).

  • Блок — самостоятельная часть страницы: header, footer, product-card.
  • Элемент — часть блока, через два подчёркивания: header__logo, product-card__title. Читается «логотип внутри шапки».
  • Модификатор — вариация, через два дефиса: tabs__button--active, gallery__dot--active.
Без методологии
/* непонятно, чьё это и где живёт */
.red { color: #c69978; }
.box2 ul li a { display: block; }
.big-title { font-size: 1.75rem; }
БЭМ
/* по имени видно роль и место */
.brand-name { color: var(--color-accent); }
.header__nav-link { display: block; }
.section__title { font-size: 1.75rem; }

Как объяснить выбор. Имя класса сразу говорит о роли и о том, в каком блоке он живёт. Стили разных блоков не пересекаются. Не нужны длинные селекторы вида header ul li a, а значит, правило легко переопределить. Один и тот же блок можно использовать на всех страницах (так у нас button и section).

Ещё одно требование ТЗ: классы называются по роли, а не по внешнему виду. .brand-name — правильно, .brown-text — неправильно.

Два класса на одном элементе. У кнопки в герое class="button hero__button". Первый класс — «как выглядит» (общий блок кнопки), второй — «где стоит» (место в сетке героя). Это нормальная практика БЭМ, она называется миксом.

10. Flexbox: выстроить элементы в ряд или столбик

Flex включается на родителе, дальше его дети выстраиваются сами:

.footer__nav {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 2.5rem;
}
  • display: flex — включить.
  • flex-direction: column — дети в столбик (по умолчанию в ряд).
  • align-items: center — выравнивание поперёк направления.
  • justify-content: center — выравнивание вдоль направления.
  • flex-wrap: wrap — если не влезают, переносить на новую строку. Это защита от горизонтальной прокрутки на телефоне.
  • gap — расстояние между детьми. Как пробел между книгами на полке.

Почему gap, а не margin. ТЗ требует задавать расстояния внутри flex- и grid-контейнеров через gap. Причина простая: margin вешается на каждый элемент и легко сбивается у крайних (лишний отступ у последнего), а gap работает только между элементами и задаётся в одном месте — на родителе.

До
.footer__nav > a { margin-right: 2.5rem; }
После
.footer__nav { gap: 2.5rem; }

11. Grid: раскладка по таблице

Flex работает в одном направлении (ряд или столбик). Grid раскладывает по строкам и колонкам одновременно. Нужен, когда есть настоящая сетка: герой главной, витрина, карточки каталога.

Схема героя в hero.css рисуется словами:

.hero {
    display: grid;
    grid-template-columns: 1.9fr 1.3fr 1fr;
    grid-template-areas:
        "photo quote   product"
        "photo .       button";
}

Читается как таблица 2×3:

┌──────────┬──────────┬──────────┐
│          │  quote   │ product  │
│  photo   ├──────────┼──────────┤
│          │    .     │  button  │
└──────────┴──────────┴──────────┘
  • fr — «доля свободного места». Первая колонка примерно вдвое шире третьей.
  • photo написано в обеих строках — фото занимает обе.
  • . — пустая клетка.
  • На элементе пишем grid-area: photo — «я стою в клетке с этим именем».

Адаптивные колонки без медиазапросов. В галерее и каталоге:

grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));

Читается: «делай столько колонок, сколько влезает, но каждая не уже 10rem». На ноутбуке три в ряд, на телефоне сами переложатся.

Когда что выбирать:

  • Один ряд или один столбик (меню, подвал, список ссылок) — flex.
  • Две оси сразу, колонки с заданными долями (герой, витрина, карточки) — grid.

12. position: где стоит элемент

  • static — по умолчанию, элемент просто в потоке.
  • relative — остаётся на месте, но становится точкой отсчёта для потомков.
  • absolute — выпадает из потока, ставится относительно ближайшего предка с position, не static.

Так устроена шапка:

.header__inner {
    position: relative;
}

.header__actions {
    position: absolute;
    top: 0;
    right: 0;
}

Иконки поиска и корзины прижаты к правому верхнему углу контейнера, а не всего экрана. Логотип и меню при этом остаются по центру, потому что иконки из потока «выпали» и не двигают остальных. Это оправданное место для position: нужно поставить элемент в угол, не влияя на соседей.

Второе применение — .visually-hidden в base.css: текст «Search» и «Cart» скрыт с экрана, но читается скринридерами.

13. :hover, :focus-visible и transition

  • :hover — состояние, когда навели мышь. У нас: пункт меню становится коричневым, кнопка заливается тёмным, иконка полупрозрачная.
  • :focus-visible — состояние, когда до элемента дошли клавишей Tab. Вокруг элемента рисуется видимая рамка, чтобы человек с клавиатуры понимал, где он находится. Обязательно по ТЗ.
  • transition — плавная смена, например цвета за 0.2 секунды, а не рывок.

Одно общее правило в base.css даёт видимый фокус всему сайту сразу:

:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 3px;
}

Чем hover отличается от focus-visible. Hover — для мыши, focus-visible — для клавиатуры. Нужны оба: часть людей мышью не пользуется вообще.

Проверка вручную: открой страницу и нажимай Tab. Рамка должна переходить по ссылкам, кнопкам и полям.

14. Адаптивная вёрстка: сайт на телефоне

Задача ТЗ: страница работает на телефоне и на компьютере без горизонтальной прокрутки и без наложения элементов. Как это достигнуто:

  1. Телефон сначала. По умолчанию всё идёт в один столбик, а на широком экране включается сложная раскладка.
  2. @media (min-width: 48rem) — «правила внутри работают, только если окно шире 48rem (около 770px)». Так включается сетка героя, витрины и about.
  3. max-width: 100% у картинок — не вылезают за контейнер.
  4. flex-wrap: wrap — меню и колонки подвала переносятся.
  5. repeat(auto-fit, minmax(...)) — колонки сами подстраиваются.
  6. max-width + margin-inline: auto у секций — на большом мониторе содержимое не растягивается шире 1200px и стоит по центру.
@media (min-width: 48rem) {
    .showcase {
        grid-template-columns: 2fr 3fr;
    }
}

Как проверить самому. Открой сайт, нажми F12, включи режим устройств (иконка телефона) и выбери ширину 375px. Горизонтальной полосы внизу быть не должно.

15. Что делает каждый файл: шпаргалка для защиты

  • base.css — «фундамент»: переменные в :root, сброс браузерных отступов, шрифт и цвет на body, img не шире контейнера, ссылки без подчёркивания, общий focus-visible, класс brand-name, класс visually-hidden.
  • header.css — шапка: flex-столбик (логотип, меню), меню flex в ряд с gap, иконки через position: absolute в углу, hover на пунктах и иконках.
  • footer.css — тёмный подвал: две колонки ссылок flex с переносом, копирайт мелким шрифтом.
  • button.css — кнопка-рамка. На hover заливается. Используется и как <a>, и как <button>.
  • section.css — общая секция: заголовок по центру, содержимое под ним. Одна на все страницы.
  • hero.css — первый экран главной: grid с именованными областями, на телефоне столбик.
  • showcase.css — две карточки рядом, колонки 2 к 3.
  • gallery.css — grid из трёх областей (стрелка, картинки, стрелка) и точки снизу.
  • tabs.css — рамка с вкладками, активная подчёркнута.
  • product-card.css — сетка карточек и сама карточка: рамка, тень, картинка одной формы.
  • post-card.css — карточка статьи блога: на телефоне столбик, на широком картинка слева.
  • split.css — универсальные «две части рядом» для about.
  • form.css — форма: поля, подписи, галочка, ссылка terms, кнопка отправки.
  • map.css — картинка-карта с ограничением ширины.

16. Что запрещено в ТЗ и как мы это соблюли

  • JavaScript нельзя. У нас нет ни одного скрипта. Поэтому стрелки галереи и вкладки каталога только выглядят как в макете, но не переключаются: для этого нужен JS.
  • CSS-фреймворки и препроцессоры нельзя. Только чистый CSS. Шрифт Barlow подключён с Google Fonts — это шрифт, а не фреймворк.
  • Атрибут style нельзя. В начале ЛР2 были два style="color: …" на названии бренда. Заменены классом brand-name.
  • !important нельзя. Нигде не используется.
  • Селекторы по #id нельзя. Только классы.
  • Фиксированная height у блоков с текстом нельзя. height задан только у иконок, точек галереи и скрытого текста — там текста нет.
  • Нельзя менять семантику на <div> и удалять содержимое. Теги header, nav, main, section, article, footer остались. <div> добавлены только как обёртки для вёрстки (например header__inner, showcase, gallery).

17. Команды и как всё запускать

Открыть сайт локально (в папке проекта):

python3 -m http.server 8000

Потом в браузере http://127.0.0.1:8000/index.html. Остановить сервер — Ctrl+C. Можно и просто открыть index.html двойным кликом, но с сервером ближе к реальному сайту.

Полезные комбинации в браузере:

  • F12 — инструменты разработчика. Вкладка Elements показывает, какие CSS-правила действуют на элемент, и их можно менять на лету.
  • Ctrl+Shift+M (в Chrome/Firefox) — режим устройств, проверка телефонной ширины.
  • Tab — проверить видимый фокус.

Git-команды, которые понадобятся:

git status          # что изменилось
git add -A          # добавить всё в коммит
git commit -m "Лабораторная работа 2: CSS-оформление"
git log --oneline   # история коммитов

Быстрая проверка запретов поиском по проекту:

grep -rn '!important' css/        # должно быть пусто
grep -n 'style=' *.html           # должно быть пусто
grep -n '<script' *.html          # должно быть пусто

18. Вопросы на защите и короткие ответы

Зачем нужны переменные в :root? Чтобы цвет или отступ был записан один раз. Поменял значение — изменилось на всём сайте. Имена по роли, поэтому они остаются верными при смене оформления.

Почему rem, а не px? rem зависит от размера шрифта в браузере. Если пользователь увеличил шрифт, всё выросло пропорционально. px этого не умеет, для доступности это хуже.

Почему БЭМ? Имя класса сразу говорит, что это и где оно живёт. Стили блоков не пересекаются. Не нужны длинные селекторы, правила легко переопределять. Блоки (button, section) переиспользуются на разных страницах.

Чем flex отличается от grid? Flex расставляет элементы по одной оси (ряд или столбик), grid — по двум сразу (строки и колонки). Меню — flex, герой и карточки — grid.

Зачем gap, а не margin? gap задаёт расстояние только между элементами и в одном месте, на родителе. margin вешается на каждый элемент и даёт лишний отступ у крайнего.

Где у вас position и зачем? В шапке. У родителя relative, у блока иконок absolute; top: 0; right: 0. Иконки встают в правый верхний угол контейнера и не двигают логотип и меню.

Как работает адаптивность? Телефонная раскладка (один столбик) по умолчанию, на широком экране включается @media (min-width: 48rem). Картинки не шире контейнера, flex-блоки с переносом, колонки auto-fit. Горизонтальной прокрутки нет.

Что такое focus-visible и зачем он? Видимая рамка у элемента, выбранного клавишей Tab. Нужна тем, кто работает с клавиатуры. Одно общее правило в base.css покрывает все ссылки, кнопки и поля.

Почему стрелки и вкладки не работают? Переключение — это JavaScript, а он в ЛР2 запрещён. Сделан только внешний вид, hover и фокус, как требует ТЗ.

Что такое каскад? Порядок решения споров между правилами: побеждает более конкретный селектор, при равенстве — написанное ниже. Поэтому !important не нужен и запрещён.

Почему у элемента box-sizing: border-box? Чтобы width включала padding и рамку. Без этого блок с padding становится шире заданного и ломает вёрстку.

Что такое <span class="mbl-term" data-term="viewport" data-def="Видимая область страницы в окне браузера. Тег meta viewport говорит телефону: ширина страницы равна ширине экрана, а не 980px по умолчанию.">viewport</span>? Видимая область страницы. Тег <meta name="viewport" content="width=device-width, initial-scale=1.0"> в <head> нужен, чтобы на телефоне работали медиазапросы. Без него телефон рисует страницу как для компьютера и уменьшает.

Что ещё стоит знать про честные ограничения

  • Шапка на главной стоит над большим фото, а в макете справа от него. Это единственное заметное отличие от Figma.
  • Стрелки, точки и вкладки только внешний вид.
  • Страницы cart.html и post.html содержат только заглушку «COOMING SOON!», в макете этих страниц нет.
Самопроверка 0 / 10
Могу разобрать любое CSS-правило на селектор, свойство и значение
Могу объяснить, какое правило победит, если два правила красят один элемент
Могу назвать четыре слоя блочной модели и объяснить, зачем нужен box-sizing border-box
Могу объяснить, чем rem лучше px для шрифтов
Могу объяснить, зачем цвета и отступы вынесены в переменные в :root
Могу объяснить выбор БЭМ и прочитать имя вида header__nav-link
Могу объяснить, когда нужен flex, а когда grid, и что делает gap
Могу объяснить, зачем в шапке position relative у родителя и absolute у иконок
Могу объяснить, зачем нужен focus-visible и чем он отличается от hover
Могу объяснить, как сайт работает на телефоне без горизонтальной прокрутки
Как усвоено?