Перейти к содержанию

Лабораторная работа №3. Стилизация интерфейса Web-приложения

1. Теория

1.0. Цель работы

Сформировать у обучающегося системное понимание CSS как слоя представления и компоновки интерфейса Web-приложения, который:

  • управляет визуальной и пространственной организацией UI (layout, типографика, цвета, состояния);
  • работает по правилам каскада, наследования и специфичности;
  • проектирует адаптивный интерфейс (mobile-first, breakpoints, responsive-паттерны);
  • обеспечивает масштабируемую архитектуру стилей (компоненты, методологии, организация файлов);
  • интегрируется с HTML (семантика, классы/атрибуты) и JavaScript (классы, состояния, анимации).

Закрепить навык архитектурного описания UI через схему:

UI (HTML) → DOM → CSS (cascade/layout) → JS (events/state) → API (HTTP) → DB

Подводка (почему это важно): В реальном проекте CSS — это не «раскраска». Это правила отображения данных и состояний. Один и тот же UI-компонент должен:

  • выглядеть одинаково во всех местах проекта,
  • вести себя предсказуемо при изменениях (добавили блок → не “поехало”),
  • корректно адаптироваться под разные экраны,
  • быть управляемым из JS через состояния (is-open, is-loading, is-error),
  • оставаться поддерживаемым командой (архитектура, методология, ограничения).

1.1. Введение в CSS

1.1.1. Назначение CSS: разделение структуры и представления

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

Подводка: если вы смешиваете HTML и CSS (например, делаете внешний вид через inline-стили), вы теряете масштабируемость: невозможно быстро поменять тему, дизайн-систему, адаптивность.

Пример “плохо” (inline-стили ломают поддержку):

<button style="background: red; color: white; padding: 12px 16px;">
  Купить
</button>

Пример “нормально” (контракт через класс):

<button class="btn btn--primary">Купить</button>
.btn { padding: 12px 16px; border-radius: 10px; }
.btn--primary { background: #e11d48; color: #fff; }

1.1.2. Принцип каскадности

Каскад — механизм, по которому браузер выбирает итоговые значения свойств, когда на элемент влияют несколько правил.

Каскад учитывает (в упрощении):

  1. важность (!important),
  2. происхождение (user-agent / user / author),
  3. специфичность селектора,
  4. порядок правил (ниже в файле → при равенстве выигрывает).

Подводка: каскад — это “разрешитель конфликтов”. Если вы не понимаете каскад, вы начинаете “лечить” стили !important, а это разрушает архитектуру.


1.1.3. Принцип наследования

Наследование — передача некоторых свойств от родителя к потомкам. Наследуются в основном текстовые свойства (color, font-family, line-height), но не layout-свойства (например, margin не наследуется).

Пример:

.page {
  font-family: system-ui, sans-serif;
  color: #111827;
  line-height: 1.5;
}
<div class="page">
  <h1>Заголовок</h1>
  <p>Текст наследует цвет и шрифт.</p>
</div>

Подводка: наследование — главный инструмент “глобальной консистентности” (типографика, цвет текста), но его нужно держать под контролем (не делать “глобальные” правила, которые ломают компоненты).


1.2. Способы подключения CSS

1.2.1. Inline (в атрибуте style)

<p style="color: #e11d48; font-weight: 700;">Акцентный текст</p>

Когда допустимо: быстрый прототип, единичная правка, письма (email), генерация стилей из CMS. Минусы: не переиспользуется, сложно сопровождать, конфликтует с архитектурой.


1.2.2. Internal (внутри <style>)

<head>
  <style>
    .card { padding: 16px; border: 1px solid #e5e7eb; }
  </style>
</head>

Подводка: в учебных мини-работах допустимо, но в проекте это усложняет кеширование и поддержку.


1.2.3. External (отдельный файл)

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

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


1.2.4. @import

@import url("./base.css");
@import url("./components/card.css");

Подводка: @import может ухудшать загрузку (особенно при каскаде импортов). В современных проектах чаще используют сборщики/бандлеры, а в “чистом CSS” — аккуратно и минимально.


1.3. Селекторы CSS

1.3.1. Базовые селекторы

По тегу:

button { cursor: pointer; }

По классу:

.btn { padding: 12px 16px; }

По id:

#app { min-height: 100vh; }

Подводка:

  • id должен быть уникальным, и стили через #id часто создают проблемы со специфичностью.
  • В UI-архитектуре чаще используют классы и data-атрибуты.

1.3.2. Комбинаторы

Потомок (любой уровень вложенности):

.card p { margin: 0; }

Прямой потомок:

.nav > a { text-decoration: none; }

Соседний элемент (+):

label + input { margin-top: 6px; }

Общий сосед (~):

input:focus ~ .hint { opacity: 1; }

Подводка: чем сложнее селектор, тем выше риск “ломкости”: небольшая правка HTML ломает стили. Поэтому архитектурно лучше иметь классы компонентов.


1.3.3. Атрибутные селекторы

input[type="email"] { border-color: #93c5fd; }
button[data-action="add-to-cart"] { font-weight: 700; }

Подводка: data-* удобно для “контракта” с JS: CSS может стилизовать, а JS — находить/управлять.


1.3.4. Псевдоклассы

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.field:focus { outline: 2px solid #60a5fa; }
.item:nth-child(2n) { background: #f9fafb; }

Подводка: псевдоклассы — это модель состояний без JS: hover/focus/active — критично для UX и доступности.


1.3.5. Псевдоэлементы

.badge::before { content: "● "; }
.card::after { content: ""; display: block; height: 1px; background: #e5e7eb; }

1.3.6. Таблица специфичности (приоритет селекторов)

Селектор Пример Специфичность (A,B,C)
Inline style style="..." выше всех (условно 1,0,0,0)
#id #header (1,0,0)
.class, [attr], :pseudo-class .btn, [type="text"], :hover (0,1,0)
tag, ::pseudo-element button, ::before (0,0,1)
*, комбинаторы *, >, +, ~ (0,0,0)

Подводка: если вы начали “побеждать” стили повышением специфичности, вы строите систему конфликтов. В архитектуре лучше управлять правилами через компоненты и слои.


1.4. Каскадность и специфичность

1.4.1. Что такое каскад (как браузер выбирает итоговый стиль)

Когда несколько правил задают одно свойство, браузер выбирает итог по схеме:

  1. !important (если есть),
  2. более специфичный селектор,
  3. правило, объявленное позже.

1.4.2. !important как крайняя мера

.btn { color: black !important; }

Подводка: !important должен быть исключением (например, утилитарные классы, временный фикс, переопределение сторонних библиотек). Иначе каскад превращается в “гонку вооружений”.


1.4.3. Порядок подключения файлов

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

Подводка: порядок — это часть архитектуры: базовые правила → компоненты → страницы → overrides.


1.4.4. Примеры конфликтов и решения

Конфликт:

button { background: #111827; color: white; }
.btn { background: #e11d48; }
<button class="btn">Купить</button>

Итог: background будет #e11d48, потому что .btn специфичнее, а color останется белым из button.

Плохое “решение” через специфичность:

body main .toolbar .btn { background: #e11d48; }

Хорошее решение (слой компонента + модификатор):

.btn { background: #111827; color: #fff; }
.btn--primary { background: #e11d48; }

1.5. Цвета и фоны

1.5.1. Базовые свойства

.text-muted { color: #6b7280; }
.card { background-color: #ffffff; }
.hero { background-image: url("./img/bg.jpg"); background-size: cover; }

1.5.2. Градиенты и прозрачность

.hero {
  background-image: linear-gradient(135deg, rgba(225,29,72,0.9), rgba(59,130,246,0.6));
}

1.5.3. CSS-переменные (custom properties)

:root {
  --color-bg: #0b1220;
  --color-text: #e5e7eb;
  --radius: 16px;
}
.page {
  background: var(--color-bg);
  color: var(--color-text);
  border-radius: var(--radius);
}

Подводка: переменные — фундамент темизации и дизайн-системы: один источник правды вместо “магических” цветов по проекту.


1.6. Работа с текстом и типографикой

body {
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.5;
}

h1 { font-size: 32px; font-weight: 800; }
p { letter-spacing: 0.2px; text-align: left; }
.upper { text-transform: uppercase; }

1.6.1. Web-шрифты (через @font-face)

@font-face {
  font-family: "MyFont";
  src: url("./fonts/MyFont.woff2") format("woff2");
  font-display: swap;
}

.title { font-family: "MyFont", system-ui, sans-serif; }

Подводка: font-display: swap снижает риск “невидимого текста” при загрузке шрифта.


1.7. Box Model (фундамент)

1.7.1. Состав Box Model

  • content — содержимое (текст/контент)
  • padding — внутренний отступ
  • border — граница
  • margin — внешний отступ

1.7.2. Схема Box Model

┌─────────────────────────────── margin ───────────────────────────────┐
│  ┌──────────────────────────── border ─────────────────────────────┐  │
│  │  ┌──────────────────────── padding ───────────────────────────┐  │  │
│  │  │                       content                              │  │  │
│  │  └────────────────────────────────────────────────────────────┘  │  │
│  └──────────────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────────────┘

1.7.3. box-sizing, overflow

/* промышленный дефолт */
*, *::before, *::after { box-sizing: border-box; }

.card {
  width: 320px;
  padding: 16px;
  border: 1px solid #e5e7eb;
  overflow: hidden;
}

Подводка: без border-box расчёты размеров становятся непредсказуемыми, особенно в сетках и адаптиве.


1.8. Позиционирование

1.8.1. Виды позиционирования

.static-example { position: static; }
.relative-example { position: relative; top: 6px; }
.absolute-example { position: absolute; top: 0; right: 0; }
.fixed-example { position: fixed; bottom: 16px; right: 16px; }
.sticky-example { position: sticky; top: 0; }

1.8.2. Практические кейсы

Бейдж на карточке (absolute внутри relative):

.card { position: relative; }
.card__badge {
  position: absolute;
  top: 12px;
  right: 12px;
  padding: 6px 10px;
  border-radius: 999px;
  background: #111827;
  color: #fff;
}

Подводка: absolute работает относительно ближайшего предка с position: relative; — это типовая основа UI-бейджей, меню, модалок.


1.9. Flexbox

1.9.1. Базовые свойства

.toolbar {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
}

1.9.2. Пример макета интерфейса на Flexbox (Header + Controls)

HTML:

<header class="toolbar">
  <h1 class="toolbar__title">Каталог</h1>
  <div class="toolbar__actions">
    <button class="btn">Фильтры</button>
    <button class="btn btn--primary">Добавить</button>
  </div>
</header>

CSS:

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.toolbar__actions {
  display: flex;
  gap: 10px;
  align-items: center;
}

Подводка: Flexbox идеален для “линейных” компоновок: панели, строки, группы кнопок, выравнивание по оси.


1.10. CSS Grid

1.10.1. Базовые свойства

.grid {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(3, 1fr);
}

1.10.2. Auto-fit / auto-fill

.catalog {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

1.10.3. Grid areas

.page {
  display: grid;
  grid-template-columns: 280px 1fr;
  grid-template-areas: "sidebar main";
  gap: 16px;
}
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }

1.10.4. Отличие Flexbox и Grid

Критерий Flexbox Grid
Модель 1-мерная (ряд/колонка) 2-мерная (ряд + колонка)
Лучше для панели, группы, выравнивание сетки карточек, layout страниц
Контроль распределение по оси точное размещение в сетке

Подводка: не “Flex или Grid”, а “Flex внутри Grid”: страница строится Grid, а внутри компонентов используется Flex.


1.11. Адаптивный дизайн

1.11.1. Media queries и breakpoints

.card { padding: 16px; }
@media (max-width: 640px) {
  .card { padding: 12px; }
}

1.11.2. Mobile-first подход

/* мобильный по умолчанию */
.layout { display: grid; gap: 12px; }

/* расширяем для больших экранов */
@media (min-width: 900px) {
  .layout { grid-template-columns: 280px 1fr; }
}

Подводка: mobile-first снижает количество переопределений: вы “наращиваете” интерфейс по мере роста экрана.


1.11.3. Пример адаптивной карточки

HTML:

<article class="product-card">
  <img class="product-card__img" src="./product.jpg" alt="Товар" />
  <div class="product-card__body">
    <h3 class="product-card__title">Сыворотка</h3>
    <p class="product-card__price">1999 ₽</p>
    <button class="btn btn--primary">В корзину</button>
  </div>
</article>

CSS:

.product-card {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 12px;
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
}

.product-card__img {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 12px;
}

@media (max-width: 520px) {
  .product-card {
    grid-template-columns: 1fr;
  }
  .product-card__img {
    width: 100%;
    height: 180px;
  }
}

1.12. Анимации и переходы

1.12.1. transition + transform

.btn {
  transition: transform 160ms ease, box-shadow 160ms ease;
}

.btn:hover {
  transform: translateY(-2px);
}

1.12.2. @keyframes + animation

@keyframes pulse {
  0% { transform: scale(1); }
  50% { transform: scale(1.03); }
  100% { transform: scale(1); }
}

.badge--pulse {
  animation: pulse 1.2s ease-in-out infinite;
}

1.12.3. Пример hover-анимации кнопки

.btn--primary {
  background: #e11d48;
  color: #fff;
  border-radius: 12px;
  padding: 12px 16px;
  transition: transform 160ms ease, filter 160ms ease;
}

.btn--primary:hover { transform: translateY(-2px); filter: brightness(1.05); }
.btn--primary:active { transform: translateY(0); }

Подводка: анимация должна быть “смысловой”: показать интерактивность и обратную связь, а не отвлекать.


1.13. Продвинутые возможности CSS

1.13.1. Переменные, calc(), clamp()

:root { --space: 16px; }

.container { padding: calc(var(--space) * 2); }

.title {
  font-size: clamp(20px, 2.5vw, 36px);
}

1.13.2. filter, backdrop-filter, object-fit

.avatar { filter: grayscale(30%); }

.glass {
  background: rgba(255,255,255,0.08);
  backdrop-filter: blur(10px);
}

.media { object-fit: cover; width: 100%; height: 240px; }

1.14. Архитектура CSS (как строится масштабируемый проект)

1.14.1. БЭМ (BEM)

Принцип: блок → элемент → модификатор.

<article class="card card--featured">
  <h3 class="card__title">Заголовок</h3>
  <p class="card__desc">Описание</p>
</article>
.card { padding: 16px; border-radius: 16px; }
.card__title { font-weight: 800; }
.card--featured { border: 2px solid #e11d48; }

Подводка: БЭМ уменьшает конфликты: стиль компонента не “протекает” на соседние элементы.


1.14.2. SMACSS / Atomic CSS / Модульный подход (идея)

Подход Идея Когда полезен
SMACSS деление стилей на категории (Base/Layout/Module/State/Theme) большие проекты
Atomic/Utility много маленьких классов-утилит быстрые UI, дизайн-система
Модульный компоненты как модули (card.css, button.css) командная разработка

1.14.3. Организация файлов проекта (пример)

styles/
  base/
    reset.css
    tokens.css
    typography.css
  components/
    button.css
    card.css
    form.css
  pages/
    catalog.css
  main.css

main.css подключает базу и компоненты (или собирается сборщиком).


1.15. Доступность и UX в CSS

1.15.1. Контраст и читабельность

Подводка: визуально красивый интерфейс может быть нечитаемым. Минимум — достаточный контраст текста и фона.

1.15.2. :focus-visible и клавиатура

.btn:focus-visible {
  outline: 3px solid #60a5fa;
  outline-offset: 2px;
}

1.15.3. hover vs touch

@media (hover: hover) {
  .btn:hover { transform: translateY(-2px); }
}

Подводка: на touch-устройствах hover может “залипать”, поэтому проверка hover: hover — взрослая практика.


1.16. Производительность CSS

1.16.1. Минимизация и критический CSS (идея)

  • минимизировать CSS для production (сборщик/минификатор),
  • выносить критические стили первого экрана (по возможности),
  • избегать огромных каскадов переопределений.

1.16.2. DevTools-анализ

Подводка: если интерфейс “тормозит”, проблема часто не в JS, а в layout/paint из-за тяжелых эффектов, огромных теней, анимаций размеров вместо transform.


1.17. Интеграция CSS с JavaScript

1.17.1. Манипуляция классами (основа)

HTML:

<button class="btn" id="toggleFilters">Фильтры</button>
<aside class="filters" id="filtersPanel"></aside>

CSS (состояние):

.filters { display: none; }
.filters.is-open { display: block; }

JS:

const btn = document.querySelector("#toggleFilters");
const panel = document.querySelector("#filtersPanel");

btn.addEventListener("click", () => {
  panel.classList.toggle("is-open");
});

Подводка: хорошая практика — управлять UI через классы состояния, а не через element.style = .... Так сохраняется архитектура и каскад.


1.17.2. Динамические стили и анимации через JS (когда уместно)

JS уместен, когда стиль зависит от вычислений/данных (например, прогресс-бар):

progressEl.style.setProperty("--progress", `${value}%`);
.progress::after { width: var(--progress); }

1.18. Типичные ошибки новичков (и как их диагностировать)

  1. Inline-стили как основной подход → нет переиспользования, хаос, сложный рефакторинг.

  2. Конфликты из-за #id и высокой специфичности → потом приходится “перебивать” селекторы ещё более сложными.

  3. !important везде → каскад перестаёт работать как система.

  4. Нет адаптивности → “на моём ноутбуке ок” ≠ работающий интерфейс.

  5. Непонимание специфичности → “почему стиль не применился?” решают не анализом, а случайными правками.

Мини-чек диагностики (как думать):

  • стиль не применился → проверь селектор (попадает ли), затем специфичность, затем порядок подключений;
  • всё “поехало” → проверь box model (box-sizing), размеры, overflow, сетку (Grid/Flex);
  • на мобилке плохо → mobile-first, breakpoints, размеры (clamp), touch/hover.

1.19. Мини-схемы для отчёта (CSS в системе UI)

1.19.1. Слои формирования интерфейса

HTML (семантика/структура)
    ↓
DOM (дерево элементов)
    ↓
CSS (каскад, специфичность, layout)
    ↓
Render tree → Layout → Paint
    ↓
UI (видимый интерфейс)

1.19.2. CSS как “контракт” со структурой и состояниями

HTML: class / data-*  ←→  CSS: компоненты/состояния  ←→  JS: toggle/state

2. Задание

Цель: закрепить каскадность и специфичность, box model, основы layout (Flexbox/Grid), адаптивность, состояния UI и доступность на материале интерфейса Web-приложения.


Смысл лабораторной работы

Переход от “разметка есть” к “интерфейс выглядит и ведёт себя как система”:

  • один и тот же компонент должен корректно выглядеть в разных местах;
  • конфликты стилей должны решаться архитектурно, а не !important;
  • layout должен быть устойчивым (не “поехал” из-за padding/border);
  • интерфейс должен быть адаптивным и управляемым через состояния (классами);
  • интерактивные элементы должны быть удобны для клавиатуры (focus).

Ключевой результат

Интерфейсная страница, в которой:

  • продемонстрирован конфликт каскада и корректное решение через “компонент + модификатор”;
  • включён глобальный box-sizing: border-box, карточки фиксированной ширины не “раздуваются”;
  • собрана header-панель на Flexbox с переносом элементов;
  • каталог карточек построен на Grid (auto-fit/minmax) и адаптирован под мобильный;
  • панель фильтров открывается/закрывается через JS (classList.toggle) и CSS-класс состояния .is-open;
  • реализован :focus-visible и проверена Tab-навигация.

2.1. Задания на закрепление теоретического материала (выполнить до стилизации предметной области)

Данный блок выполняется в отдельной песочнице practice-lr3.css (или внизу styles.css), чтобы студент мог показать понимание каскада, box model и layout до финальной стилизации проекта.

Требования к оформлению:

  • к каждому блоку должен быть маленький демонстрационный HTML-фрагмент (можно прямо в index.html внутри секции “practice”);
  • запрещено использовать !important (в этой ЛР это отдельное правило);
  • обязательно сделать комментарии в CSS: что конфликтует и почему выигрывает.

2.1.1. Каскад и конфликты

Задача A1. Создать 3 правила, конфликтующие по одному свойству (color) для одного и того же элемента.

HTML (пример):

<section class="practice">
  <p id="demoText" class="text text--accent">Текст для проверки каскада</p>
</section>

CSS (пример конфликта):

/* Правило 1 — по тегу (низкая специфичность) */
p { color: #111827; }

/* Правило 2 — по классу (выше) */
.text { color: #2563eb; }

/* Правило 3 — по id (ещё выше) */
#demoText { color: #e11d48; }

Что требуется показать в отчёте:

  1. какие 3 правила задают color;
  2. почему выиграло конкретное (специфичность);
  3. что будет, если поменять порядок правил (для одинаковой специфичности).

Задача A2. Доказать влияние порядка при одинаковой специфичности.

CSS (пример):

.text--accent { color: #16a34a; }  /* правило выше */
.text--accent { color: #f97316; }  /* правило ниже — должно выиграть */

Ожидаемый результат:

  • цвет текста — #f97316, потому что специфичность одинаковая, выигрывает правило ниже.

Задача A3. Убрать конфликт архитектурно: “компонент + модификатор”, без !important.

HTML (пример):

<p class="label label--danger">Статус: Ошибка</p>
<p class="label label--success">Статус: Успех</p>

CSS (архитектурное решение):

.label {
  font-weight: 700;
  padding: 6px 10px;
  border-radius: 999px;
  color: #111827; /* базовое */
}

.label--danger { color: #b91c1c; }
.label--success { color: #166534; }

Ожидаемый результат:

  • нет “борьбы селекторов”, внешний вид управляется модификаторами.

2.1.2. Box Model и стабильная сетка

Задача B1. Подключить box-sizing: border-box глобально.

CSS (обязательно):

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

Подводка (что проверяем): при border-box ширина элемента включает padding и border, поэтому карточки в сетке ведут себя предсказуемо.


Задача B2. Сделать карточку товара шириной 320px с padding и border, и проверить, что ширина не растёт.

HTML (пример):

<div class="card-demo">Карточка 320px</div>

CSS (пример):

.card-demo {
  width: 320px;
  padding: 16px;
  border: 2px solid #e5e7eb;
}

Что проверить:

  • в DevTools → computed width должен быть 320px, а не 320 + padding + border.

2.1.3. Макет на Flexbox (header-панель)

Задача C1. Собрать header-панель: заголовок слева, кнопки справа.

HTML (пример):

<header class="toolbar">
  <h1 class="toolbar__title">Каталог</h1>

  <div class="toolbar__actions">
    <button class="btn">Фильтры</button>
    <button class="btn btn--primary">Добавить</button>
  </div>
</header>

CSS (пример):

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.toolbar__actions {
  display: flex;
  gap: 10px;
  align-items: center;
}

Задача C2. Добавить flex-wrap, чтобы на узком экране кнопки переносились.

CSS (добавить):

.toolbar {
  flex-wrap: wrap;
}

.toolbar__actions {
  flex-wrap: wrap;
}

Ожидаемый результат:

  • при уменьшении ширины окна кнопки не ломают layout и уходят на новую строку.

2.1.4. Grid-сетка каталога + адаптив

Задача D1. Сделать сетку карточек auto-fit/minmax.

HTML (пример):

<section class="catalog">
  <article class="product-card">Карточка 1</article>
  <article class="product-card">Карточка 2</article>
  <article class="product-card">Карточка 3</article>
  <article class="product-card">Карточка 4</article>
</section>

CSS (пример):

.catalog {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.product-card {
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
}

Задача D2. Добавить breakpoint для мобильного: карточка становится “в столбик” (перестройка внутренней структуры карточки).

HTML (пример карточки с медиа + контентом):

<article class="product-card2">
  <img class="product-card2__img" src="./product.jpg" alt="Товар" />
  <div class="product-card2__body">
    <h3 class="product-card2__title">Название</h3>
    <p class="product-card2__meta">Цена · Статус</p>
    <button class="btn btn--primary">В корзину</button>
  </div>
</article>

CSS (пример):

.product-card2 {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 12px;
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
}

.product-card2__img {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 12px;
}

@media (max-width: 520px) {
  .product-card2 {
    grid-template-columns: 1fr; /* “в столбик” */
  }

  .product-card2__img {
    width: 100%;
    height: 180px;
  }
}

Ожидаемый результат:

  • на мобильном изображение сверху, контент ниже, ничего не выходит за границы.

2.1.5. Интеграция с JS (состояния)

Задача E1. Реализовать панель фильтров, которая открывается/закрывается через classList.toggle.

HTML (пример):

<button class="btn" id="btnFilters" type="button">Фильтры</button>

<aside class="filters" id="filtersPanel" aria-hidden="true">
  <h2 class="filters__title">Фильтры</h2>
  <label class="field">
    Статус
    <select>
      <option>Все</option>
      <option>new</option>
      <option>done</option>
    </select>
  </label>
</aside>

CSS (состояние):

.filters {
  display: none;
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
}

.filters.is-open {
  display: block;
}

JS (пример):

const btn = document.querySelector("#btnFilters");
const panel = document.querySelector("#filtersPanel");

btn.addEventListener("click", () => {
  panel.classList.toggle("is-open");

  const isOpen = panel.classList.contains("is-open");
  panel.setAttribute("aria-hidden", String(!isOpen));
});

Ожидаемый результат:

  • открытие/закрытие происходит через класс состояния, а не через inline-стили.

2.1.6. Доступность

Задача F1. Добавить :focus-visible для интерактивных элементов.

CSS (пример):

.btn:focus-visible,
a:focus-visible,
select:focus-visible,
input:focus-visible {
  outline: 3px solid #60a5fa;
  outline-offset: 2px;
}

Задача F2. Проверить Tab-навигацию.

Требования к проверке (описать в отчёте):

  1. Нажать Tab и пройти по кнопкам/полям в логичном порядке.
  2. Фокус должен быть видимым на каждом элементе.
  3. Не должно быть “ловушек фокуса” (когда Tab застревает).

Ожидаемый результат:

  • пользователь может управлять интерфейсом без мыши.

2.2. Исходные данные (предметная область)

Для выполнения лабораторной работы используется предметная область, закреплённая за студентом на семестр (как в ЛР-1/ЛР-2).

Требования:

  • интерфейс должен содержать: header-панель, панель фильтров, каталог карточек;
  • карточки должны быть реального смысла для предметной области (товары/заявки/курсы/записи и т.д.);
  • минимум 6–8 карточек для демонстрации Grid-сетки и адаптива.

2.3. Структура проекта (рекомендуется)

Задание: организовать стили и скрипт так, чтобы было понятно, где что лежит.

Минимальный набор файлов:

  • index.html
  • styles.css (или styles/main.css)
  • script.js (только для toggle фильтров)

Подключение:

<link rel="stylesheet" href="./styles.css" />
<script src="./script.js" defer></script>

Ожидаемый результат:

  • страница открывается без ошибок, стили применяются, toggle работает.

2.4. Обязательные требования к сдаваемому UI

Студент обязан продемонстрировать:

  1. Каскад и конфликт: 3 правила на одно свойство + объяснение победителя (специфичность/порядок).
  2. Архитектурное решение: компонент + модификатор вместо “войны селекторов”, без !important.
  3. Box model стабильность: border-box глобально + карточка 320px не раздувается.
  4. Flex header: заголовок слева, кнопки справа + перенос.
  5. Grid каталог: auto-fit/minmax + адаптив внутренней карточки на мобильном.
  6. Состояния через класс: .is-open для панели фильтров, управление через JS toggle.
  7. Доступность: :focus-visible + проверка Tab-навигации.

3. Итоговая задача: “Стилизация интерфейса Web-приложения” (ЛР-1 → ЛР-2 → ЛР-3)

Цель: превратить интерфейс из ЛР-1/ЛР-2 в визуально организованный, адаптивный и управляемый по состояниям UI.


3.1. Модификация верстки index.html

Добавить (или оформить) следующие блоки:

  1. header.toolbar (заголовок + кнопки).
  2. aside.filters (панель фильтров).
  3. section.catalog (сетку карточек).
  4. Демонстрационный блок для каскада (можно скрыть в конце страницы).

3.2. Требования к логике и архитектуре

  • стили должны быть читабельны и компонентны (классы, модификаторы);
  • запрещено “лечить” конфликты !important;
  • все состояния UI реализуются классами (.is-open, при желании .is-active, .is-loading);
  • JS делает только переключение состояния (toggle) — дизайн остаётся в CSS.

3.3. Требования к сдаче

В репозитории должны быть:

  • index.html (структура интерфейса);
  • styles.css (все блокиреализованы);
  • script.js (toggle фильтров);
  • краткое описание в README.md: что и где демонстрируется (A–F).

3.3. Для выполнения требуется

  1. Шаблон отчёта, указанный в описании лабораторной работы.
  2. Вариант задания, закреплённый за студентом на семестр и опубликованный в ЛМС.
  3. Требования к оформлению — согласно методическим указаниям в ЛМС или соответствующему разделу документации.
  4. Ссылка на репозиторий (или архив проекта).

3.4. Запрет

Запрещается использование систем искусственного интеллекта для выполнения лабораторной работы.

Работы, выполненные с использованием ИИ, полностью или частично скопированные, а также не сданные, оцениваются в 0 баллов без возможности доработки.


4. Контрольные вопросы

  1. Что такое CSS и какую роль он играет в связке HTML → DOM → CSSOM → Render Tree → Layout → Paint?
  2. Что такое каскад в CSS и какие факторы определяют итоговое значение свойства (важность, специфичность, порядок)?
  3. Что такое специфичность селекторов и почему селектор #id обычно “сильнее”, чем .class?
  4. В каких случаях влияет порядок правил в файле, и как это доказать на примере двух одинаковых селекторов?
  5. Почему использование !important считается плохой практикой в архитектуре стилей, и чем его заменяют (компонент + модификатор)?
  6. Что такое Box Model и из каких частей он состоит (content/padding/border/margin)?
  7. Зачем подключают глобально box-sizing: border-box и как это влияет на расчёт ширины элемента?
  8. В чём разница между Flexbox и Grid: какая модель 1-мерная, какая 2-мерная, и где что применять?

5. Чек-лист для самопроверки

Баллы Критерии оценки
20 Выполнены все обязательные блоки и они явно демонстрируются на странице (есть HTML для проверки + комментарии в CSS). Реализовано: конфликт 3 правил по одному свойству + доказательство победителя (специфичность и порядок) и устранение конфликта архитектурно (компонент + модификатор) без !important; глобальный box-sizing: border-box + карточка шириной 320px с padding и border, ширина не растёт (проверено в DevTools); header на Flexbox (заголовок слева, кнопки справа) + flex-wrap и корректный перенос на узком экране; каталог на Grid (repeat(auto-fit, minmax(...))) + breakpoint, где карточка перестраивается “в столбик” на мобильном; панель фильтров открывается/закрывается через classList.toggle() и CSS-класс .is-open (без inline-стилей), желательно синхронизирован aria-hidden; добавлен :focus-visible для интерактивных элементов и выполнена проверка Tab-навигации (фокус виден, порядок логичный). Проект оформлен аккуратно: классы компонентные (БЭМ/модификаторы), селекторы не “ломкие”, нет !important. Файлы (index.html, styles.css, script.js) подключены корректно, ошибок в консоли нет, изменения зафиксированы в репозитории.
16–19 Почти всё выполнено: блоки есть, но допущены 1–2 недочёта. Примеры каскада/порядка показаны, но объяснение в отчёте неполное; или архитектурное решение “компонент + модификатор” сделано, но местами остаются конфликтные/слишком специфичные селекторы; или border-box включён, но проверка ширины карточки 320px в DevTools не описана; или адаптив реализован, но breakpoint не доведён (карточка не полностью “в столбик”); или toggle работает, но состояние оформлено частично. В целом интерфейс рабочий и близок к требованиям.
12–15 Выполнено частично: присутствуют минимум 3–4 блока, но не все. Частая ситуация: есть Grid и Flex, но нет доказательства каскада (A) или нет архитектурного устранения конфликта; или есть border-box, но карточка 320px сделана неверно; или toggle реализован, но стили открытого состояния сделаны inline; или нет :focus-visible. Рабочий результат есть, но требования закрыты не полностью.
8–11 Есть базовая стилизация и отдельные элементы layout (например, только Flex или только Grid), но отсутствуют обязательные демонстрации: нет корректного блока (конфликт + доказательство), нет проверки box model, адаптив поверхностный, toggle/состояния не оформлены через классы, доступность не проверена.
1–7 Формальная работа: подключён CSS, но нет системной реализации заданий A–F. Стили “разрозненные”, каскад/специфичность не продемонстрированы, layout неустойчивый, адаптив отсутствует, JS toggle не работает или отсутствует, фокус не виден.
0 Работа не сдана / неработоспособна (страница не открывается, критические ошибки) / не соответствует требованиям оформления.

6. Шаблон отчёта

👉 Скачать шаблон отчёта