Перейти до змісту

CSS — Каскадні таблиці стилів

CSS (Cascading Style Sheets) визначає як виглядає HTML-розмітка. HTML — кістяк, CSS — одяг і косметика. Три способи підключення: inline, <style>, зовнішній файл (рекомендований).


🧠 Ментальна модель: CSS — це НЕ мова програмування. Це мова ДЕКЛАРАЦІЙ. Ти не кажеш браузеру "зроби це кроком 1, кроком 2". Ти кажеш: "я ХОЧУ, щоб цей елемент виглядав так" — а браузер вже сам вирішує як це досягти. Як замовлення в ресторані: ти говориш що хочеш з'їсти, а кухар вирішує як приготувати.

📚 Чому це існує: До CSS вся стилізація вбудовувалась прямо в HTML через атрибути (<font color="red">). Це призвело до жахливого дублювання — щоб змінити колір заголовків на всьому сайті, треба було редагувати тисячі тегів. CSS відокремив "що показати" від "як показати".

🌐 CSSOM — паралельне дерево до DOM: Так само як HTML будує DOM (Document Object Model), браузер будує CSSOM (CSS Object Model) — паралельне дерево правил стилів. Тільки коли обидва дерева готові, браузер об'єднує їх у Render Tree і малює сторінку. Тому <link rel="stylesheet"> в <head> блокує рендеринг — браузер не намалює жодного пікселя, поки не завантажить і не розбере CSS. Повільний CSS = білий екран для користувача.

❌ Типова помилка початківця: Вважати, що CSS "виконується" зверху вниз як Python. Насправді браузер спочатку збирає ВСІ правила, потім застосовує їх разом, вирішуючи конфлікти через каскад і специфічність. Порядок важливий тільки при однаковій специфічності.


0. Архітектура Frontend та Конвеєр Рендерингу

Separation of Concerns — як у backend

У backend ти звик до чіткого розділення відповідальності: - Model — дані - View — логіка - Template — представлення

У браузері те саме:

Шар Відповідальність
HTML Структура та семантика даних
CSS Візуальне представлення
JavaScript Поведінка та динамічні зміни

CSS ніколи не змінює HTML — він змінює DOM-вузли у пам'яті.


Rendering Pipeline — від байтів до пікселів

Цей діаграм показує повний шлях від твого Django-шаблону до пікселів на екрані. Зверни увагу, що HTML і CSS обробляються паралельно — це одна з причин, чому CSS у <head> блокуючий: браузер хоче мати CSSOM готовим одночасно з DOM.

[Django Template] --> HTTP Response (HTML-рядок)
                            |
                            ▼
                  ┌─── Browser Parser ───┐
                  │                      │
               [HTML]                 [CSS]
                  │                      │
                  ▼                      ▼
             (DOM Tree)            (CSSOM Tree)
                  │                      │
                  └──────────┬───────────┘
                             ▼
                       (Render Tree)
                       (тільки видимі елементи)
                             │
                             ▼
                    [Layout / Reflow]
                    (обчислення розмірів і позицій)
                             │
                             ▼
                        [Paint]
                    (пікселі на екрані)

Детальні етапи

1. Парсинг DOM Браузер читає HTML зверху вниз, будує дерево об'єктів у пам'яті. Кожен тег → вузол (Node). <p>Текст</p> → два вузли: Element + Text.

2. Парсинг CSSOM Паралельно читається CSS (зі <style> або зовнішніх .css). Будується CSS Object Model — дерево правил з обчисленими значеннями.

3. Render Tree DOM + CSSOM об'єднуються. Виключаються елементи display: none та <head>. Кожен видимий вузол отримує стиль.

4. Layout (Reflow) Браузер обчислює точні координати і розміри кожного елемента. Результат: прямокутник (x, y, width, height) для кожного вузла.

5. Paint Заповнення пікселів: кольори, фони, рамки, тіні, текст.


Layout Thrashing — критична пастка продуктивності

Layout Thrashing — це коли JavaScript почергово читає і записує DOM-властивості, змушуючи браузер робити synchronous reflow після кожного читання. Аналогія: уяви офіціанта, якого ти запитуєш "скільки вільних столиків?" → він рахує → ти займаєш один → знову запитуєш → він знову рахує → і так по колу. Набагато краще: порахувати всі вільні місця одразу, а потім зайняти кілька.

// ❌ ПОГАНО — Layout Thrashing
// Кожне читання offsetHeight змушує браузер робити synchronous reflow
for (const el of elements) {
    const h = el.offsetHeight;         // браузер перераховує layout
    el.style.height = (h + 10) + 'px'; // потім знову змінює
}

// ✅ ДОБРЕ — batch read, потім batch write
const heights = elements.map(el => el.offsetHeight);  // всі читання
heights.forEach((h, i) => {
    elements[i].style.height = (h + 10) + 'px';       // всі записи
});

Властивості що тригерять reflow: offsetHeight, offsetWidth, getBoundingClientRect(), scrollTop, clientWidth, getComputedStyle().

Правило: Для backend-розробника Layout Thrashing — це аналог N+1 у ORM: замість одного запиту — сотні. Завжди групуй читання окремо від записів.


1. Підключення CSS

Є три способи підключити CSS. Зовнішній файл — найкращий, бо він кешується браузером: після першого завантаження CSS-файл зберігається локально, і наступні сторінки сайту завантажуються швидше. <style> в <head> корисний для невеликих критичних стилів. Inline — тільки для динамічних значень з Django context, бо він має найвищу специфічність і його важко перевизначити.

<!-- 1. Зовнішній файл (найкращий спосіб — кешується браузером) -->
<link rel="stylesheet" href="style.css">

<!-- 2. Блок <style> в <head> (для невеликих стилів або компонентів) -->
<style>
    body { font-family: Arial; }
</style>

<!-- 3. Inline стиль (тільки для динамічних значень через Django context) -->
<div style="background-color: {{ user.color }}; width: 100px;">...</div>

2. Анатомія CSS-правила

Кожне CSS-правило складається з трьох частин: селектор (кого стилізувати), властивість (що змінити) і значення (на що змінити). Крапка з комою в кінці кожного оголошення — обов'язкова, без неї браузер "не бачить" наступне правило. Коментарі в CSS пишуться тільки через /* */, не через //.

/* Селектор { властивість: значення; } */

h1 {
    color: #2c3e50;          /* колір тексту */
    font-size: 2rem;         /* розмір шрифту */
    font-weight: bold;       /* товщина шрифту */
    margin-bottom: 1rem;     /* відступ знизу */
}


🧠 Ментальна модель: CSS-селектори — це як SQL WHERE для DOM. .card p = "SELECT всі <p>, що знаходяться ВСЕРЕДИНІ елемента з класом .card". Ти не визначаєш що робити — ти визначаєш КОГО чіпати.

📚 Чому це існує: Без селекторів довелось би стилізувати кожен елемент окремо через inline-стилі. Селектори дозволяють написати одне правило, яке автоматично застосовується до сотень відповідних елементів — і коли ти їх додаєш, стилі підхоплюються автоматично.

🌐 Як браузер обробляє це: Браузер читає складені селектори справа наліво для ефективності. .card p → спочатку знаходить усі <p> на сторінці, потім перевіряє чи кожен з них має предка .card. Тому уникай надмір глибоких селекторів — браузер перевіряє кожен елемент.

❌ Типова помилка початківця: Плутати .btn.active (без пробілу — обидва класи на ОДНОМУ елементі) і .btn .active (з пробілом — .active ВСЕРЕДИНІ .btn). Пробіл — це комбінатор "нащадок", і він повністю змінює значення селектора.


3. Селектори — як вибрати елементи

Базові селектори

Базові селектори — це фундамент. Тег вибирає всі елементи цього типу (обережно — дуже широко). Клас (.) — найпоширеніший спосіб, один елемент може мати кілька класів. ID (#) — унікальний на сторінці, використовуй для JS-хуків або anchor-посилань, а не для стилізації. Атрибутні селектори дозволяють фільтрувати елементи по вмісту атрибутів — корисно для форм.

/* Тег — всі елементи цього типу */
p { color: #333; }
h1, h2, h3 { font-family: Georgia; }   /* кілька через кому */

/* Клас — починається з . */
.card { border: 1px solid #ddd; }
.btn.btn-primary { background: blue; } /* два класи одночасно (без пробілу) */

/* ID — починається з # — унікальний */
#header { background: #000; }

/* Атрибут */
input[type="text"] { border: 2px solid blue; }
a[href^="https"] { color: green; }     /* href починається з https */
a[href$=".pdf"] { color: red; }        /* href закінчується на .pdf */
a[href*="google"] { font-weight: bold; } /* href містить google */

Комбінатори (відносини між елементами)

Комбінатори описують відносини між елементами в DOM-дереві. Пробіл (нащадок) — найширший: вибирає елементи на будь-якій глибині вкладеності. > (прямий нащадок) — суворіший: тільки безпосередні діти. + і ~ — для сусідніх елементів. Знай різницю: .nav > li не зачепить li в підменю, а .nav li — зачепить все.

/* Нащадок (будь-який рівень вкладеності) */
.card p { color: #666; }

/* Прямий нащадок */
nav > ul { padding: 0; }

/* Сусідній елемент (одразу після) */
h2 + p { font-size: 1.1rem; }

/* Всі наступні сусіди */
h2 ~ p { color: #555; }

Псевдокласи

Псевдокласи дозволяють стилізувати елементи залежно від їх стану або позиції в DOM. Стани посилань (:hover, :active) — реакція на дії користувача. :focus — критично важливо для доступності (keyboard navigation). Структурні псевдокласи (:nth-child) — потужний інструмент для зебрових таблиць та складних списків без JavaScript.

/* Стан посилань */
a:link    { color: blue; }     /* непереглянуте */
a:visited { color: purple; }   /* переглянуте */
a:hover   { color: red; }      /* при наведенні миші */
a:active  { color: orange; }   /* при натисканні */

/* Фокус (для keyboard navigation та форм) */
input:focus { outline: 2px solid blue; border-color: blue; }
button:focus-visible { outline: 3px solid orange; }

/* Структурні */
li:first-child  { font-weight: bold; }
li:last-child   { border-bottom: none; }
li:nth-child(2) { background: #f0f0f0; }     /* 2-й елемент */
li:nth-child(odd)  { background: #fafafa; }  /* непарні */
li:nth-child(even) { background: #f0f0f0; }  /* парні */
li:nth-child(3n)   { color: red; }           /* кожен 3-й */

/* Пусті/непусті */
p:empty { display: none; }
input:not([disabled]) { cursor: pointer; }

/* Форми */
input:valid   { border-color: green; }
input:invalid { border-color: red; }
input:required::after { content: " *"; color: red; }
input:disabled { opacity: 0.5; cursor: not-allowed; }
input:checked + label { font-weight: bold; }

Псевдоелементи

Псевдоелементи (з подвійним ::) створюють "віртуальні" дочірні елементи, яких немає в HTML, але вони рендеряться у браузері. ::before і ::after вимагають властивість content — навіть якщо вона порожня (content: ""). Вони корисні для декоративних елементів (іконки, лічильники), щоб не засмічувати HTML.

/* Вміст до/після елемента */
.card::before { content: "★ "; color: gold; }
.required::after { content: " *"; color: red; }

/* Перший рядок / перший символ */
p::first-line   { font-variant: small-caps; }
p::first-letter { font-size: 3rem; float: left; }

/* Виділений текст */
::selection { background: #ffd700; color: #000; }

/* Placeholder в input */
input::placeholder { color: #aaa; font-style: italic; }


🧠 Ментальна модель: Специфічність — це 4-значне число (a, b, c, d), де кожна цифра відповідає рівню селектора: (inline, ID, class/attr/pseudo-class, tag/pseudo-element). Порівнюй як числа: (0,1,0,0) = 100, (0,0,10,0) = 100 теж — але ID завжди виграє у класів, бо порівняння йде зліва направо, поцифрово. Десять класів не переб'ють один ID.

📚 Чому це існує: Без специфічності браузер не знав би яке правило застосувати, якщо два правила конфліктують. Специфічність — це детермінований алгоритм вирішення конфліктів, що гарантує передбачуваний результат незалежно від порядку підключення CSS-файлів.

🌐 Як браузер обробляє це: Браузер збирає ВСІ правила для елемента, обчислює специфічність кожного, сортує та застосовує переможця. При рівній специфічності перемагає правило, оголошене пізніше у файлі (або в файлі, підключеному пізніше).

❌ Типова помилка початківця: Використовувати !important щоб "виправити" конфлікт специфічності. Це як кричати на офіціанта замість того, щоб зрозуміти меню. !important ламає каскад і перетворює CSS у хаос: коли вже все важливо — нічого не важливо. Правильне рішення — зрозуміти чому виникає конфлікт і виправити структуру.


4. Специфічність — алгоритм вирішення конфліктів

Як CSS вирішує конфлікти (Cascade Algorithm)

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

1. !important оголошення              (найвищий)
2. Inline стилі   (style="...")
3. ID-селектори   (#header)
4. Класи / Атрибути / Псевдокласи    (.card, [type], :hover)
5. Теги / Псевдоелементи             (p, h1, ::before)
6. Успадковані стилі від батьків     (найнижчий)

При однаковій специфічності виграє правило оголошене останнім у файлі.

Математика специфічності

Ось як браузер розраховує "вагу" кожного селектора. Перша цифра (inline) завжди найсильніша — навіть один inline-стиль перебиває тисячу CSS-правил. ID перебиває будь-яку кількість класів. Це не просто "більше = краще" — це ієрархія рівнів.

Inline style    →  (1, 0, 0, 0)  = 1000
#id             →  (0, 1, 0, 0)  = 0100
.class / [attr] →  (0, 0, 1, 0)  = 0010
tag             →  (0, 0, 0, 1)  = 0001

Нижче — практичне порівняння: зверни увагу, як поєднання селекторів додає їх специфічності. p.text = тег (0,0,0,1) + клас (0,0,1,0) = (0,0,1,1). Це більше ніж просто .text (0,0,1,0), але менше ніж #intro (0,1,0,0).

p                   { color: black; }    /* (0,0,0,1) =    1 */
.text               { color: blue;  }    /* (0,0,1,0) =   10 */
p.text              { color: red;   }    /* (0,0,1,1) =   11 */
#intro              { color: red;   }    /* (0,1,0,0) =  100 */
p#intro             { font-size: 100%; } /* (0,1,0,1) =  101 */
p { font-size: 75%; }                    /* (0,0,0,1) =    1 */

/* !important — скасовує алгоритм, уникати */
p { color: pink !important; }           /* перебиває всі без !important */

Prediction Exercise

Цей приклад — класичний тест на розуміння специфічності. Спробуй сам вирішити ПЕРЕД тим, як дивитись відповідь. Кожна властивість вирішується незалежно — color і font-size можуть прийти з різних правил.

<p id="intro" class="text">Текст</p>
/* Правило 1 */ p            { color: black;     font-size: 75%;  }
/* Правило 2 */ .text        { color: blue;                       }
/* Правило 3 */ #intro       { color: red;                        }
/* Правило 4 */ p#intro      { font-size: 100%;                   }

Питання: Яким буде фінальний вигляд елемента?

Відповідь (розбір по властивостях):

Властивість Переможець Чому
color #intro → red (0,1,0,0)=100 > .text (0,0,1,0)=10 > p (0,0,0,1)=1
font-size p#intro → 100% (0,1,0,1)=101 > p (0,0,0,1)=1

Підсумок: <p> буде червоним і з розміром 100%.

DevTools підказка: якщо не розумієш чому стиль не застосовується → F12 → вкладка Styles (перекреслені правила програли в специфічності) або вкладка Computed (фінальне значення після вирішення всього каскаду).



🧠 Ментальна модель: Кожен HTML-елемент — це КОРОБКА. Завжди. Навіть рядок тексту — це коробка. Навіть невидима точка — це коробка. Flexbox та Grid — це системи розміщення цих коробок. Box Model описує з чого складається КОЖНА коробка: чотири шари від центру назовні.

📚 Чому це існує: Box Model стандартизував як браузери розраховують розміри елементів. До нього різні браузери рахували розміри по-різному (IE Wars). Сьогодні всі браузери дотримуються одного стандарту, але є два режими: content-box (за стандартом) і border-box (практичний).

🌐 Як браузер обробляє це: Під час фази Layout браузер обчислює прямокутник кожного елемента. Якщо box-sizing: content-box → width = розмір content, і до нього ДОДАЮТЬСЯ padding + border. Якщо box-sizing: border-box → width = розмір від краю до краю border, padding і border ВКЛЮЧЕНІ у width. Bootstrap глобально встановлює border-box через * { box-sizing: border-box; }.

❌ Типова помилка початківця: Задати width: 300px; padding: 20px; і дивуватись, що елемент має 340px ширини (300 + 20 + 20). Це поведінка content-box. З border-box елемент буде рівно 300px — padding "стискає" вміст всередині. Завжди додавай box-sizing: border-box на початку свого CSS.


5. Box Model — фундамент верстки

Уяви картину в рамі на стіні. content — сама картина. padding — паспарту (білий матеріал між картиною і рамою). border — сама рама. margin — простір стіни навколо рами, до сусідніх картин. Браузер завжди рахує фізичний розмір елемента як content + padding + border. Margin — це простір ЗА межами елемента.

┌─────────────────────────────────────────┐
│                 margin                  │  ← зовнішній відступ (прозорий)
│   ┌─────────────────────────────────┐   │
│   │              border             │   │  ← рамка
│   │   ┌─────────────────────────┐   │   │
│   │   │          padding        │   │   │  ← внутрішній відступ
│   │   │   ┌─────────────────┐   │   │   │
│   │   │   │     content     │   │   │   │  ← вміст (width × height)
│   │   │   └─────────────────┘   │   │   │
│   │   └─────────────────────────┘   │   │
│   └─────────────────────────────────┘   │
└─────────────────────────────────────────┘

Зверни особливу увагу на рядок box-sizing: border-box — це одна з найважливіших практик. Bootstrap автоматично додає її до всіх елементів через скидання стилів. Якщо пишеш CSS без Bootstrap — додай сам. margin: 0 auto на блочному елементі з фіксованою шириною центрує його горизонтально.

.box {
    /* Розмір вмісту */
    width: 300px;
    height: 200px;

    /* Внутрішній відступ (між вмістом і рамкою) */
    padding: 20px;             /* всі 4 сторони */
    padding: 10px 20px;        /* вертикально горизонтально */
    padding: 10px 20px 15px 5px; /* top right bottom left (за годинниковою) */
    padding-top: 10px;
    padding-bottom: 15px;

    /* Рамка */
    border: 1px solid #ddd;
    border-radius: 8px;        /* заокруглення кутів */
    border-top: 3px solid blue;

    /* Зовнішній відступ (від елементу до сусідів) */
    margin: 20px;
    margin: 0 auto;            /* центрування блоку горизонтально */
    margin-bottom: 16px;
}

/* Важливо: box-sizing */
* {
    box-sizing: border-box;    /* width включає padding і border (Bootstrap робить це за замовчуванням) */
}
/* За замовчуванням: box-sizing: content-box; — width = тільки вміст */

6. Одиниці вимірювання

Одиниця Що означає Коли використовувати
px Пікселі — абсолютна Рамки, тіні, дрібні деталі
rem Відносно розміру шрифту <html> (зазвичай 16px) Шрифти, відступи — адаптивно
em Відносно розміру шрифту батьківського елемента Компоненти що мають масштабуватись
% Відсоток від батьківського елемента Ширина колонок, fluid layouts
vw Viewport Width — % від ширини вікна Повноекранні секції
vh Viewport Height — % від висоти вікна Hero секції, full-screen
fr Fraction — частка у CSS Grid Grid колонки/рядки
ch Ширина символу "0" Обмеження ширини тексту
auto Браузер вираховує сам Margin для центрування, height

7. Відображення елементів — display

Властивість display визначає як елемент бере участь у потоці документа. block займає весь рядок. inline — тільки свій вміст, по рядку. inline-block — як inline, але можна задавати розміри. none — повне видалення з потоку (місце не займає, на відміну від visibility: hidden). flex і grid — сучасні системи верстки.

/* Block — займає всю ширину, нова строка до і після */
div, p, h1, section, article { display: block; }

/* Inline — рівно по вмісту, в рядку з іншими inline-елементами */
span, a, strong, img { display: inline; }
/* Не можна задавати width/height inline-елементам */

/* Inline-block — в рядку, але можна задавати width/height */
.badge { display: inline-block; padding: 4px 8px; }

/* None — приховати елемент (не займає місця) */
.hidden { display: none; }  /* vs visibility: hidden; — займає місце але невидимий */

/* Flex — гнучкий контейнер (наступний розділ) */
.nav { display: flex; }

/* Grid — сіткова система (після flex) */
.layout { display: grid; }


🧠 Ментальна модель: Flexbox — це 1D система верстки. Вона розставляє елементи вздовж ОДНІЄЇ осі одночасно (або рядок, або стовпець). Ключове: є КОНТЕЙНЕР (display: flex) і є ЕЛЕМЕНТИ (всі прямі діти). Контейнер керує розміщенням, елементи підпорядковуються — але кожен елемент може сам перевизначити деякі правила через align-self, order, flex.

📚 Чому це існує: До Flexbox розробники верстали горизонтальні ряди через float: left (потребував clearfix — хак із overflow: hidden) або display: inline-block (мав зазори між елементами через пробіли в HTML, які прибирали через font-size: 0 на батьку). Flexbox — чиста, семантична заміна цих костилів.

🌐 Як браузер обробляє це: Браузер визначає головну вісь (flex-direction) і поперечну (перпендикулярна). justify-content управляє розподілом ВЗДОВЖ головної осі. align-items управляє вирівнюванням ВЗДОВЖ поперечної осі. Це два незалежних параметри, і плутанина між ними — найпоширеніша помилка новачків.

❌ Типова помилка початківця: Не розуміти вісі. Якщо flex-direction: column, то justify-content стає ВЕРТИКАЛЬНИМ (вздовж стовпця), а align-items — ГОРИЗОНТАЛЬНИМ. Багато новачків думають що justify-content: center завжди центрує горизонтально — ні, тільки вздовж головної осі.


8. Flexbox — однонапрямна гнучка верстка

Перед тим як читати код — зрозумій архітектуру Flexbox через ASCII-діаграму. Головна вісь (main axis) — це напрямок flex-direction. Поперечна вісь (cross axis) — перпендикулярна до неї. justify-content завжди управляє головною віссю. align-items завжди управляє поперечною. Запам'ятай це — і Flexbox стане передбачуваним.

Main axis (flex-direction: row) →
┌─────────────────────────────────────┐
│ [item1] [item2] [item3]             │  ← justify-content контролює розподіл тут
└─────────────────────────────────────┘
↑
align-items контролює вирівнювання тут (cross axis — вертикально для row)

Якщо змінити flex-direction: column, вісі міняються місцями:

Cross axis →
┌──────────┐
│ [item1]  │  ← align-items контролює тут (горизонтально для column)
│ [item2]  │
│ [item3]  │
└──────────┘
↓ Main axis (justify-content контролює вертикально для column)

Нижче — повний набір властивостей Flexbox з поясненнями. Почни з розуміння display: flex, flex-direction, justify-content, align-items — це 80% всіх випадків використання.

/* Контейнер */
.flex-container {
    display: flex;
    flex-direction: row;            /* row | column | row-reverse | column-reverse */
    justify-content: space-between; /* вирівнювання по головній осі */
    align-items: center;            /* вирівнювання по поперечній осі */
    flex-wrap: wrap;                /* перенос на новий рядок якщо не влізає */
    gap: 16px;                      /* відстань між дочірніми елементами */
}

/* justify-content значення:
   flex-start  → ← ← ←  (зліва)
   flex-end    →     → →  (справа)
   center      →   →→→   (по центру)
   space-between → ← → ← → ← (рівна відстань між, без крайніх)
   space-around  →  ←→ ←→ ←→  (рівна відстань навколо кожного)
   space-evenly  → ← → ← → ← → (рівна відстань між всіма включно з краями)
*/

/* align-items значення:
   flex-start  → елементи зверху
   flex-end    → елементи знизу
   center      → по центру вертикально
   stretch     → розтягнути по висоті (за замовчуванням)
   baseline    → по базовій лінії тексту
*/

/* Дочірній елемент */
.flex-item {
    flex: 1;           /* grow: 1, shrink: 1, basis: auto — займає рівну частину */
    flex: 0 0 200px;   /* фіксована ширина 200px, не росте, не стискається */
    flex-grow: 2;      /* росте в 2 рази більше за інші */
    align-self: flex-start; /* перевизначення align-items для одного елемента */
    order: -1;         /* зміна порядку (за замовчуванням 0) */
}

Типові патерни Flexbox

Ці патерни — шаблони, які ти будеш використовувати щодня. Navbar з логотипом і меню по різних боках — space-between. Картки що переносяться на новий рядок — flex-wrap: wrap з flex: 1 1 300px. Центрування по екрану — justify-content: center + align-items: center + min-height: 100vh. Sticky footer — flex-direction: column на body і flex: 1 на main.

/* Navbar */
.navbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 20px;
}

/* Картки в рядок з переносом */
.cards {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
}
.card { flex: 1 1 300px; } /* мінімум 300px, інакше переноситься */

/* Центрування елемента */
.centered {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;     /* по всій висоті екрану */
}

/* Footer внизу сторінки */
body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
main { flex: 1; } /* займає весь залишок висоти */
footer { /* автоматично внизу */ }


🧠 Ментальна модель: Grid — це 2D система верстки. На відміну від Flexbox (одна вісь), Grid управляє рядками І колонками одночасно. Уяви електронну таблицю Excel: ти визначаєш скільки колонок і їх ширини, скільки рядків і їх висоти, а потім розміщуєш елементи в "клітинки" цієї таблиці.

📚 Чому це існує: Перед Grid складні макети (sidebar + content + header + footer) вимагали складних хаків з float, таблицями або абсолютним позиціонуванням. CSS Grid — перша нативна 2D система верстки у браузері, яка робить складні макети тривіальними.

🌐 Як браузер обробляє це: Браузер будує невидиму сітку ліній (grid lines). grid-template-columns: 250px 1fr = дві вертикальні "залізничні рейки" що ділять простір на дві колонки. Елементи розміщуються між цими лініями. fr = "fractional unit" — частка від залишкового простору після вирахування фіксованих розмірів.

❌ Типова помилка початківця: Намагатись вирішити все через Flexbox, коли задача двовимірна. Правило вибору просте: якщо думаєш про ряди І колонки одночасно (макет сторінки, dashboard) → Grid. Якщо думаєш про один напрямок (кнопки в рядок, список карток) → Flexbox.


9. CSS Grid — двовимірна сіткова верстка

Коли використовувати Grid vs Flexbox — це одне з найважливіших рішень у верстці: - Flexbox: "у мене є елементи, розстав їх в рядок або стовпець" (1D: навбар, група кнопок, рядок карток) - Grid: "у мене є сторінка з рядками І колонками одночасно" (2D: дашборд, журнальна верстка, Holy Grail Layout)

Підказка: якщо ти думаєш про рядки І колонки — Grid. Якщо тільки про один напрямок — Flexbox.

/* Контейнер */
.grid-container {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;      /* 3 колонки: 25% | 50% | 25% */
    grid-template-columns: repeat(3, 1fr);   /* 3 рівні колонки */
    grid-template-columns: 250px auto 1fr;   /* фіксована, авто, залишок */
    grid-template-rows: auto 1fr auto;       /* header, main, footer */
    gap: 20px;                               /* між колонками і рядками */
    column-gap: 20px;
    row-gap: 10px;
}

/* Дочірній елемент — розміщення */
.header  { grid-column: 1 / -1; }  /* від 1-ї до останньої колонки */
.sidebar { grid-column: 1; grid-row: 2; }
.main    { grid-column: 2 / 4; }
.footer  { grid-column: 1 / -1; }

/* Іменовані зони */
.layout {
    display: grid;
    grid-template-areas:
        "header header header"
        "sidebar main main"
        "footer footer footer";
    grid-template-columns: 250px 1fr 1fr;
}
header { grid-area: header; }
.sidebar { grid-area: sidebar; }
main { grid-area: main; }
footer { grid-area: footer; }


🧠 Ментальна модель: position: static — елемент у нормальному потоці документа, як будинки на вулиці. Кожен стоїть на своєму місці. position: relative — будинок рухається, але його "місце" на вулиці зарезервовано. position: absolute — будинок "піднявся" в повітря і більше не займає місця в потоці. position: fixed — як рекламний щит, приклеєний до вашого вікна: завжди на тому ж місці незалежно від прокрутки.

📚 Чому це існує: Для overlay-елементів (модалки, tooltips, dropdown меню, sticky навбар) потрібна можливість вийти з нормального потоку. position дає цю силу — але з великою силою приходить велика відповідальність: позиційовані елементи треба відстежувати, бо вони можуть перекрити інший вміст.

🌐 Як браузер обробляє це: Позиційовані елементи (не static) створюють "стекінг контекст" і беруть участь у z-index сортуванні. absolute елемент шукає найближчого предка з position != static — і розміщується відносно нього. Якщо такого предка немає — розміщується відносно <html>.

❌ Типова помилка початківця: Елемент з position: absolute "вилітає" в неправильне місце. Причина: забули додати position: relative на батьківський елемент. Завжди перевіряй: батько позиційованого absolute-елемента повинен мати position: relative (або absolute/fixed/sticky).


10. Позиціонування

position — одна з найпотужніших і найнебезпечніших властивостей CSS. static — стандарт. relative — рухає елемент від його звичайного місця, але зберігає "резерв" місця. absolute — повністю виривається з потоку, розміщується відносно найближчого positioned предка. fixed — прикріплений до viewport, ідеально для sticky навбарів. sticky — комбінація: веде себе як relative поки не досягне порогу прокрутки, потім "прилипає".

.element {
    position: static;    /* за замовчуванням — в потоці документа */
    position: relative;  /* відносно своєї нормальної позиції (top/left/right/bottom) */
    position: absolute;  /* відносно найближчого non-static батька */
    position: fixed;     /* відносно viewport (не прокручується) */
    position: sticky;    /* relative + fixed (прилипає при прокрутці) */
}

/* Sticky navbar */
nav {
    position: sticky;
    top: 0;
    z-index: 100;       /* шар: більше = поверх */
    background: white;
}

/* Overlay поверх зображення */
.image-container {
    position: relative;
}
.overlay {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;   /* розтягнути на весь батько */
    background: rgba(0, 0, 0, 0.5);
}

/* Центрування через absolute */
.centered {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
}


🧠 Ментальна модель: Media queries — це IF-оператори для CSS. @media (min-width: 768px) { ... } = "ЯКЩО ширина вікна >= 768px, ТОДІ застосуй ці стилі". Браузер постійно перевіряє ці умови і миттєво перемикає стилі при resize вікна.

📚 Чому Mobile First існує: Мобільний трафік давно перевищив десктопний. Крім того, Mobile First — краща архітектурна стратегія: базовий CSS для простого мобільного пристрою, потім ДОДАЄМО складності для більших екранів. Це простіше ніж починати з десктопу і СКАСОВУВАТИ складні стилі для мобільних. Менше скасувань = менше багів.

🌐 Як браузер обробляє це: Браузер завантажує ВСЕ CSS-правила завжди. Media queries не "не завантажуються" — вони просто не застосовуються. Тому <link media="print"> завантажується завжди, просто не застосовується до екрану.

❌ Типова помилка початківця: Змішувати Desktop First і Mobile First в одному файлі — деякі правила через max-width, деякі через min-width. Це створює непередбачувані конфлікти. Вибери ONE підхід і дотримуйся його. Bootstrap використовує min-width (Mobile First) — якщо пишеш разом з Bootstrap, роби так само.


11. Адаптивний дизайн — Media Queries

Media query — це умова для CSS. Стилі всередині @media застосовуються тільки якщо умова виконується. min-width = "від цього розміру і більше" (Mobile First). max-width = "до цього розміру" (Desktop First). Починай з Mobile First — це стандарт Bootstrap і сучасної веб-розробки.

/* Mobile First підхід (Bootstrap використовує цей!) */

/* За замовчуванням — стилі для мобільних */
.container { padding: 16px; }
.card { width: 100%; }

/* Планшет (≥768px) */
@media (min-width: 768px) {
    .card { width: 48%; display: inline-block; }
}

/* Десктоп (≥992px) */
@media (min-width: 992px) {
    .container { max-width: 960px; margin: 0 auto; }
    .card { width: 30%; }
}

/* Великий екран (≥1200px) */
@media (min-width: 1200px) {
    .container { max-width: 1140px; }
}

/* Орієнтація */
@media (orientation: landscape) { ... }

/* Темна тема */
@media (prefers-color-scheme: dark) {
    body { background: #1a1a1a; color: #fff; }
}

/* Reduced motion (accessibility) */
@media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
}

11b. Mobile First та Bootstrap Breakpoints

Архітектурна логіка Mobile First

Mobile First означає: базовий CSS пишеться для найменшого екрану, потім через min-width медіа-запити прогресивно розширюється для більших.

❌ Desktop First (застарілий підхід):
   Пишеш для великих → override для малих → більше коду, більше конфліктів

✅ Mobile First (сучасний):
   Пишеш для малих → додаєш для великих → менше коду, ніяких скасувань

Ось чому Mobile First простіший. При Desktop First ти пишеш складний layout для десктопу, а потім намагаєшся "спростити" його для мобільного через max-width — і часто забуваєш скасувати деякі правила. При Mobile First ти починаєш з простого (один стовпець) і ДОДАЄШ складність для більших екранів.

/* Mobile First: база — мобільний */
.card { width: 100%; padding: 12px; }

/* Планшет і більше */
@media (min-width: 768px) {
    .card { width: 48%; padding: 16px; }
}

/* Десктоп і більше */
@media (min-width: 992px) {
    .card { width: 31%; padding: 20px; }
}

Чому min-width, а не max-width?

/* max-width = Desktop First — ти скасовуєш стилі для маленьких */
@media (max-width: 768px) { ... }

/* min-width = Mobile First — ти додаєш стилі для великих */
@media (min-width: 768px) { ... }

Bootstrap 5 — Breakpoints

Bootstrap будує свою сітку на тих самих min-width медіа-запитах:

Prefix Breakpoint min-width Типовий пристрій
(none) xs 0px Телефони (портрет)
sm Small 576px Телефони (альбом)
md Medium 768px Планшети
lg Large 992px Десктопи
xl X-Large 1200px Великі монітори
xxl XX-Large 1400px Дуже великі екрани

Bootstrap-класи читаються зліва направо як Mobile First декларації. col-12 = 100% на всіх екранах за замовчуванням. col-md-6 = 50% на планшетах і більше. col-lg-4 = 33% на десктопах і більше. Кожен наступний breakpoint перевизначає попередній.

<!-- Читання класів: Mobile First = зліва направо = менший → більший -->
<div class="col-12 col-md-6 col-lg-4">Контент</div>

Як браузер читає цей код:

Ширина 0–767px   → col-12 → 12/12 = 100% (мобільний, 1 блок на рядок)
Ширина 768–991px → col-md-6 → 6/12 = 50%  (планшет, 2 блоки на рядок)
Ширина 992px+    → col-lg-4 → 4/12 = 33%  (десктоп, 3 блоки на рядок)

Префікс більшого екрана завжди перебиває менший, бо в CSS медіа-запит з більшим min-width оголошений пізніше і має вищий пріоритет.

Prediction Quiz — Bootstrap Grid

Сценарій: у рядку .row — три однакових блоки:

<div class="row">
    <div class="col-12 col-md-6 col-lg-4">Блок 1</div>
    <div class="col-12 col-md-6 col-lg-4">Блок 2</div>
    <div class="col-12 col-md-6 col-lg-4">Блок 3</div>
</div>

Питання: Скільки блоків у рядку на планшеті (800px) та моніторі (1200px)?

Варіанти: - А) Планшет — 1 блок (100%), монітор — 2 блоки (50%) - Б) Планшет — 2 блоки (50%), монітор — 3 блоки (33.3%) - В) На обох пристроях — 1 блок (col-12 завжди перемагає)

Відповідь: Б

800px → col-md-6 активний → кожен блок = 6/12 = 50%
        6 + 6 = 12 → 2 блоки влазять, 3-й переноситься на новий рядок

1200px → col-lg-4 активний → кожен блок = 4/12 = 33.3%
         4 + 4 + 4 = 12 → всі 3 блоки в одному рядку ✓

Чому В неправильна: col-12 — це base клас для xs (0px+). Коли екран досягає 768px, col-md-6 перевизначає col-12 через вищий min-width. Клас col-12 не "жорстко фіксує" — він просто база, яку перебивають більші breakpoints.

Адаптивні утиліти Bootstrap (відступи, d-none d-lg-block, flex-column flex-md-row тощо) описані в BOOTSTRAP_5.md §4 — Bootstrap Utilities.


12. CSS-змінні (Custom Properties)

CSS змінні (Custom Properties) — це іменовані значення, оголошені через -- і використовувані через var(). Їх перевага над Sass-змінними: вони живі (можна змінювати через JavaScript) і успадковуються по DOM. Оголошення в :root робить їх глобальними. Оголошення в компоненті — локальними.

/* Оголошення в :root — глобальні змінні */
:root {
    --color-primary: #007bff;
    --color-secondary: #6c757d;
    --color-success: #28a745;
    --color-danger: #dc3545;
    --font-size-base: 16px;
    --border-radius: 8px;
    --shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

/* Використання */
.btn-primary {
    background: var(--color-primary);
    border-radius: var(--border-radius);
    box-shadow: var(--shadow);
}

/* Перевизначення для компонента */
.dark-card {
    --color-primary: #4dabf7;  /* локальна версія */
}

13. Типографіка

Типографіка в CSS — це набагато більше ніж просто font-size. font-family з "стековим" набором гарантує запасний шрифт якщо основний не завантажиться. line-height: 1.5 без одиниць — краща практика: воно масштабується пропорційно з font-size. rem для розмірів шрифтів дозволяє користувачу масштабувати текст через налаштування браузера (accessibility).

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
                 'Helvetica Neue', Arial, sans-serif;
    font-size: 16px;          /* базовий розмір */
    line-height: 1.5;         /* відстань між рядками (безрозмірне = x від font-size) */
    color: #212529;
}

/* Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
body { font-family: 'Roboto', sans-serif; }

h1 { font-size: 2.5rem;   line-height: 1.2; }
h2 { font-size: 2rem;     line-height: 1.3; }
h3 { font-size: 1.75rem;  line-height: 1.4; }
p  { font-size: 1rem;     line-height: 1.6; margin-bottom: 1rem; }

/* Текстові утиліти */
.text-center    { text-align: center; }
.text-uppercase { text-transform: uppercase; }
.text-truncate  { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.text-muted     { color: #6c757d; }


🧠 Ментальна модель: Holy Grail — це "Святий Грааль" верстки не просто так. Роками (1990-і — 2010-і) верстальники намагались реалізувати цей класичний макет (header + sidebar + main content + footer) чистим CSS — і не могли зробити це елегантно. Float-хаки, таблиці, абсолютне позиціонування — все давало або неповну функціональність, або жахливий код. CSS Grid нарешті вирішив задачу в 10 рядків.

📚 Чому macro/micro layout: Grid — для МАКРО (де знаходяться великі блоки сторінки). Flexbox — для МІКРО (як вміст розподіляється всередині блоку). Це не конкуренти — вони доповнюють один одного. Типова сторінка: Grid для page layout, Flexbox для navbar, Grid або Flexbox для карток, Flexbox для форм.

🌐 Як браузер обробляє це: Grid-контейнер і Flex-контейнер можуть бути вкладені один в одного. .header { display: grid; ... } і всередині .header { display: flex; ... } — це нормально. Браузер обробляє кожен контейнер незалежно.

❌ Типова помилка початківця: Намагатись зробити весь layout тільки Flexbox або тільки Grid. Правильна відповідь — поєднувати. Grid для загальної структури сторінки, Flexbox для компонентів всередині.


14. Holy Grail Layout — Класичний макет сторінки

Класичний патерн "Святий Грааль": Header + Sidebar + Content + Footer. Найкраще пояснює поєднання CSS Grid (макро) і Flexbox (мікро).

HTML — тільки структура, без позиціонування

HTML в цьому патерні — чиста семантика. Ніяких float, position, або inline-стилів. Весь layout — в CSS. Зверни увагу: порядок елементів в HTML може не відповідати візуальному порядку — Grid дозволяє розміщувати їх де завгодно.

<div class="app-layout">
    <header class="header">Логотип та Навігація</header>
    <aside class="sidebar">Бокове меню</aside>
    <main class="content">Основний вміст</main>
    <footer class="footer">Копірайт</footer>
</div>

CSS Grid — макро-архітектура (будівля)

Grid визначає "несучі стіни" — де знаходяться великі блоки. 1fr означає "весь залишковий простір". grid-column: 1 / -1 означає "від першої до останньої лінії" — тобто на всю ширину. -1 — індекс з кінця, як від'ємні індекси у Python.

.app-layout {
    display: grid;
    /* 2 колонки: фіксована ліва + гнучка права */
    grid-template-columns: 250px 1fr;
    /* 3 рядки: header | content | footer */
    grid-template-rows: 60px 1fr 50px;
    min-height: 100vh;         /* вся висота екрану */
}

/* Header і footer — на всю ширину (обидві колонки) */
.header { grid-column: 1 / 3; }   /* від 1-ї до 3-ї вертикальної лінії */
.footer { grid-column: 1 / 3; }

/* sidebar і content — по одній колонці (за замовчуванням) */
/* sidebar → колонка 1, content → колонка 2 */

Як браузер рахує 1fr: 1. Фіксований простір: 250px + 60px + 50px 2. 1fr = весь залишок viewport — це безпечніше ніж calc(100% - 250px)

+---------------------------------------+
|                HEADER                 |  ← 60px, span 2 columns
+-----------+---------------------------+
|           |                           |
|  SIDEBAR  |         CONTENT           |  ← 1fr (займає залишок висоти)
|  (250px)  |         (1fr)             |
|           |                           |
+-----------+---------------------------+
|                FOOTER                 |  ← 50px, span 2 columns
+---------------------------------------+

Flexbox — мікро-архітектура (меблі в кімнаті)

Grid задав розміри шапки. Тепер Flexbox розставляє вміст всередині шапки. Це ключовий патерн: Grid + Flexbox на одному елементі одночасно — Grid grid-area властивість контролює ЯК елемент розміщується в grid, а display: flex контролює ЯК вміст всередині елемента розподіляється.

.header {
    grid-column: 1 / 3;          /* Grid: займає обидві колонки */
    display: flex;               /* Flexbox: розставляємо вміст всередині */
    justify-content: space-between;  /* логотип зліва, меню справа */
    align-items: center;         /* вертикально по центру */
    padding: 0 20px;
    background: #2c3e50;
    color: white;
}

Ключова інтуїція: Grid керує тим, ЯКИЙ РОЗМІР займає шапка відносно всієї сторінки. Flexbox керує тим, як ВМІСТ ШАПКИ розподіляється всередині неї.

Адаптивність — Mobile First

На мобільному немає місця для sidebar поруч з content — вони мають бути в стовпець. Ця адаптивна версія перемикається між одноколоночним (мобільний) і двоколоночним (десктоп) layout через media query. Браузер виконує Reflow при кожній зміні розміру вікна.

/* Мобільний (за замовчуванням) — одна колонка */
.app-layout {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: 60px auto 1fr 50px;
    min-height: 100vh;
}
.header, .footer { grid-column: 1 / 2; }

/* Десктоп (≥768px) — дві колонки */
@media (min-width: 768px) {
    .app-layout {
        grid-template-columns: 250px 1fr;
        grid-template-rows: 60px 1fr 50px;
    }
    .header { grid-column: 1 / 3; }
    .footer { grid-column: 1 / 3; }
}

Що відбувається при resize: Коли viewport < 768px → браузер перебудовує (Reflow) сітку на льоту. Sidebar стає вище content — вертикальний стек для мобільного.

Sticky Header з z-index

Sticky header прилипає до верху при прокрутці. z-index: 100 гарантує, що він буде поверх sidebar і content. Важливо: z-index без position не працює. Sticky header автоматично отримує position: sticky, тому z-index тут діє.

.header {
    grid-column: 1 / 3;
    position: sticky;   /* прилипає при прокрутці */
    top: 0;
    z-index: 100;       /* поверх sidebar і content під час прокрутки */
    background: #2c3e50;
}
/* Важливо: z-index працює тільки на позиційованих елементах
   (position: relative/absolute/fixed/sticky) */

15. Практичні патерни

Картка (card)

Картка — найпоширеніший UI-компонент. Ці стилі демонструють декілька важливих технік: border-radius для заокруглених кутів, box-shadow для тіні глибини, overflow: hidden щоб зображення не виходило за rounded corners, transition для плавної анімації при hover, і transform: translateY для ефекту "підняття" картки — дешева анімація що не тригерить reflow.

.card {
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
    overflow: hidden;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.card-img { width: 100%; height: 200px; object-fit: cover; }
.card-body { padding: 16px 20px; }
.card-title { font-size: 1.25rem; font-weight: 600; margin-bottom: 8px; }
.card-text { color: #666; line-height: 1.6; }

Кнопка

Кнопка з display: inline-flex — сучасний підхід: дозволяє вирівнювати іконки і текст по вертикалі через align-items: center. transition: all 0.2s ease додає плавність. transform: translateY(-1px) при hover дає ефект натискання. cursor: pointer — важливо для <button> і <a> що стилізовані як кнопки.

.btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 20px;
    border: none;
    border-radius: 6px;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    text-decoration: none;
    transition: all 0.2s ease;
}
.btn-primary { background: #007bff; color: #fff; }
.btn-primary:hover { background: #0056b3; transform: translateY(-1px); }
.btn-primary:active { transform: translateY(0); }


🧠 Ментальна модель: CSS дебагінг — це детективна робота. У тебе є "злочин" (стиль не застосовується або виглядає неправильно) і є докази (DevTools). Твоє завдання: знайти "злочинця" (правило, що перемагає) через систематичне звуження кола підозрюваних.

📚 Чому це існує: CSS — декларативна мова без виключень і стектрейсів. Браузер не каже "помилка в рядку 42". DevTools — єдиний інструмент, що дає видимість у те, що браузер реально застосовує. Без DevTools CSS дебагінг — це сліпа гра.

🌐 Як браузер обробляє це: Вкладка Styles в DevTools показує ВСІ правила для елемента, відсортовані по специфічності, з перекресленими тими що програли. Computed — показує фінальне значення після вирішення всього каскаду. Layout — Box Model візуально.

❌ Типова помилка початківця: Змінювати CSS наосліп, перезавантажувати сторінку і дивуватись. Завжди починай з DevTools. 5 хвилин в DevTools заощаджують 50 хвилин сліпих правок.


16. Дебагінг CSS — Debugging Intuition

Перш ніж читати алгоритм — запам'ятай систематичний чеклист для будь-якої CSS-проблеми. Слідуй цим крокам по порядку:

CSS не працює? Слідуй цьому чеклисту:
1. Hard reload: Ctrl+Shift+R (очищає кеш — найчастіша причина "старих" стилів)
2. F12 → Elements → клікни на елемент
3. Перевір панель Styles: чи є твоє правило там?
   - Немає → CSS-файл не завантажено (перевір Network tab)
   - Перекреслено → конфлікт специфічності (перевір яке правило перемогло)
4. Перевір панель Computed → побач ФІНАЛЬНЕ значення
5. Перевір Box Model у панелі Layout

Алгоритм дебагінгу: "Стиль не застосовується"

Стиль не працює → ти змінив CSS, але браузер показує старе?
│
├── 1. Чи файл зберігається?
│       Ctrl+S → оновити браузер (Ctrl+Shift+R — hard reload без кешу)
│
├── 2. Відкрий DevTools (F12) → вкладка Elements → клікни на елемент
│
├── 3. Дивись на вкладку Styles (праворуч)
│       ┌─────────────────────────────────────┐
│       │ .card { ... }                       │  ← активне правило
│       │ ~~p { color: black; }~~             │  ← перекреслено = програло
│       │ ~~.old-class { padding: 5px; }~~    │  ← перекреслено = скасовано
│       └─────────────────────────────────────┘
│
├── 4. Якщо твоє правило перекреслено → специфічність нижча
│       Подивись що перемагає → збільш специфічність або перевір порядок
│
├── 5. Якщо правила взагалі немає в Styles → CSS не завантажено
│       F12 → Network → знайди свій .css файл → статус 200? 404?
│       Django: чи підключено {% load static %} + {% static 'css/...' %}?
│
└── 6. Вкладка Computed → фінальне значення після всього каскаду
        "Which properties actually apply to this element right now?"

DevTools — покрокова інструкція

F12 → Elements tab:

┌─── HTML структура ──────────────┬─── CSS правила ──────────────────┐
│ ▼ <body>                        │ Styles  Computed  Layout  ...    │
│   ▼ <main>                      │                                  │
│     ▼ <div class="card"> ←──── │ element.style { }               │
│         <h2>Заголовок</h2>     │                                  │
│         <p>Текст</p>            │ .card {                         │
│                                 │   border-radius: 12px;           │
│                                 │   box-shadow: ...;               │
│                                 │ }                                │
│                                 │                                  │
│                                 │ ~~div { color: red; }~~         │
│                                 │    ↑ перекреслено = програло    │
└─────────────────────────────────┴──────────────────────────────────┘

Box Model у вкладці Layout:

┌──────────────────────────────────────────┐
│              margin: 16px                │
│   ┌──────────────────────────────────┐   │
│   │           border: 1px            │   │
│   │   ┌──────────────────────────┐   │   │
│   │   │      padding: 20px       │   │   │
│   │   │   ┌──────────────────┐   │   │   │
│   │   │   │  300 × 150 px    │   │   │   │
│   │   │   └──────────────────┘   │   │   │
│   │   └──────────────────────────┘   │   │
│   └──────────────────────────────────┘   │
└──────────────────────────────────────────┘

Клікни на будь-яке число в Box Model — зміниш значення прямо в DevTools.

Типові CSS-баги та їх причини

Симптом Причина Рішення
Стиль не застосовується Нижча специфічність DevTools → Styles → знайди правило що перемогло
width: 200px але елемент ширший box-sizing: content-box Додай box-sizing: border-box
margin-top не працює між двома div Margin collapse (злиття відступів) Використай padding, overflow: auto, або display: flex
z-index не піднімає елемент Немає position: relative/absolute/fixed Додай position: relative
Flex-дочірній розтягнутий по висоті align-items: stretch (за замовчуванням) align-items: flex-start або align-self: flex-start
inline-block елементи мають зазор Whitespace між тегами в HTML Видали пробіли, або font-size: 0 на батьку
overflow: hidden обрізає тінь box-shadow виходить за межі overflow Перенеси тінь на батьківський елемент
position: sticky не "прилипає" Батько має overflow: hidden/auto Прибери overflow з батька
CSS-анімація тримтить на мобільних Анімується top/left замість transform Заміни на transform: translateX/Y

Margin Collapse — найпідступніший баг

<div style="margin-bottom: 30px">Блок 1</div>
<div style="margin-top: 20px">Блок 2</div>

Очікування: відстань між блоками = 30 + 20 = 50px Реальність: відстань = max(30, 20) = 30px — відступи зливаються!

Margin Collapse відбувається між:
  - сусідніми block-елементами по вертикалі
  - батьком і першим/останнім дочірнім елементом (якщо немає border/padding)

Не відбувається:
  - в flex-контейнерах
  - в grid-контейнерах
  - між inline-block елементами
  - якщо між ними є border або padding

Рішення:

/* Варіант 1: додай padding замість margin */
.parent { padding-top: 1px; }  /* розриває злиття */

/* Варіант 2: зроби батька flex-контейнером */
.parent { display: flex; flex-direction: column; }

/* Варіант 3: overflow (хак, уникати) */
.parent { overflow: auto; }

Stacking Context — контекст накладання (z-index)

z-index не глобальний — він працює всередині свого stacking context.

<div style="position: relative; z-index: 1;">   <!-- Контекст A -->
    <div style="position: relative; z-index: 999;">
        <!-- z-index=999, але він всередині контексту A (z-index=1) -->
    </div>
</div>

<div style="position: relative; z-index: 2;">   <!-- Контекст B -->
    <div style="position: relative; z-index: 1;">
        <!-- z-index=1, але весь контекст B (z-index=2) поверх A -->
    </div>
</div>

Результат: Елемент з z-index: 1 з контексту B буде поверх елемента з z-index: 999 з контексту A, тому що контекст B (z-index: 2) вище за контекст A (z-index: 1).

Що створює новий stacking context: - position + z-index (не auto) - opacity < 1 - transform, filter, clip-path - will-change - isolation: isolate

/* Ізоляція компонента від глобального z-index */
.modal-wrapper {
    isolation: isolate;  /* ← явно створюємо контекст, захищає від витікання */
}


🧠 Ментальна модель: Продуктивність CSS — це про те, наскільки дорогі операції ти змушуєш браузер виконувати. Деякі CSS-зміни коштують 1 одиницю роботи (opacity), деякі — 1000 одиниць (зміна width перераховує весь layout). Знаючи "вартість", ти можеш писати анімації і стилі, що не "гальмують" браузер.

📚 Чому CSS performance важливо для Django: Якщо твій CSS-файл 500KB (незмінений Bootstrap + кастомні стилі), користувач бачить білий екран ВСЕ цей час завантаження, бо CSS блокує рендеринг. Рішення: мінімізуй (видаляй пробіли/коментарі), використовуй тільки потрібні компоненти Bootstrap, підключай критичний CSS inline, а решту — async.

🌐 GPU layers: Деякі CSS властивості (transform, opacity, filter) запускають на GPU (Graphics Processing Unit) замість CPU. GPU спеціалізований на паралельній обробці пікселів — анімації на GPU набагато плавніші (60fps vs 15fps). will-change: transform "просить" браузер перемістити елемент на GPU layer заздалегідь.

❌ Типова помилка початківця: Анімувати top/left/width/height замість transform. top: 0 → top: 100px тригерить Layout (найдорожча операція). transform: translateY(0) → translateY(100px) обробляється GPU без Layout — у десятки разів дешевше.


17. CSS Performance — Що відбувається під капотом

Critical Rendering Path

Ще раз про Critical Rendering Path — тепер з фокусом на продуктивність. CSS — "рендер-блокуючий" ресурс. Браузер не намалює нічого, поки не завантажить і не обробить весь CSS у <head>. Тому: мінімізуй розмір CSS, використовуй CDN для Bootstrap (кешується між сайтами), розглянь Critical CSS (inline найважливіший CSS прямо в HTML).

HTML parsing → DOM
                 \
                  → Render Tree → Layout → Paint → Composite
                 /
CSS parsing → CSSOM

Блокуючий рендеринг: <link rel="stylesheet"> блокує рендеринг до завантаження CSS. Тому Bootstrap CDN підключають в <head> — браузер не малює нічого, поки не знає стилі.

Властивості по вартості

Знання "вартості" CSS-властивостей допомагає писати ефективні анімації. Загальне правило: якщо CSS-зміна вимагає перерахунку позицій інших елементів (Layout/Reflow) — це дорого. Якщо тільки перемальовує пікселі без зміни позицій — дешевше. Якщо обробляється GPU без участі CPU — найдешевше.

Дешево (не тригерить layout):
  opacity, transform, filter, color, background-color

Дорого (тригерить Reflow — перерахунок позицій):
  width, height, margin, padding, font-size, border,
  top, left, right, bottom

Найдорожче (тригерить синхронний reflow в JS):
  el.offsetHeight, el.clientWidth, el.getBoundingClientRect()

Ось конкретний приклад чому transform ефективніший за left для анімацій. Обидва виглядають однаково для користувача, але один навантажує CPU і тригерить Layout, а інший — легкий GPU-рендеринг.

/* ❌ Анімація яка тригерить Layout → дорого */
@keyframes slide { from { left: 0; } to { left: 100px; } }

/* ✅ Анімація тільки на Composite layer → дешево */
@keyframes slide { from { transform: translateX(0); } to { transform: translateX(100px); } }

will-change — підказка браузеру

will-change — це "попередження" браузеру: "цей елемент скоро зміниться". Браузер може заздалегідь перемістити його на GPU layer, що робить майбутню анімацію плавнішою. Але зловживати не можна: кожен GPU layer споживає відеопам'ять. Додавай will-change тільки до елементів що БУДУТЬ анімуватись, і прибирай після завершення анімації.

/* Перемістити елемент на GPU layer заздалегідь */
.animated-card {
    will-change: transform;  /* браузер промотуватиме на GPU layer */
}
/* Прибери will-change коли анімація завершилась — вона споживає пам'ять GPU */

18. Питання для самоперевірки

  1. Браузер отримав HTML і CSS одночасно. Що будується першим — DOM чи CSSOM?
  2. Яка різниця між display: none і visibility: hidden? Який тригерить reflow?
  3. Є два правила: .card p { color: blue; } і #main .card p { color: red; }. Яке переможе?
  4. Чому margin: 0 auto центрує блок горизонтально, але не вертикально?
  5. Елемент з position: absolute та z-index: 999 ховається за елемент з z-index: 1. Чому?
  6. Чому width: 100% на flex-дочірньому елементі іноді не займає весь рядок?
  7. @media (min-width: 768px) проти @media (max-width: 768px) — яка різниця в підходах?
  8. Є клас Bootstrap col-12 col-md-6. На екрані 900px — яка ширина?
  9. Чому transition: all 0.3s є поганою практикою для продуктивності?
  10. Які три властивості CSS можна анімувати без тригеру reflow?