Компонент · рівень 2

Галерея

Головне фото товару й чотири мініатюри під ним. Найвізуальніший компонент продукту й водночас найтихіший: банка стоїть у світлі, а не на білому, і нічого більше на цьому місці не відбувається.

рівень 2design/system/components/gallery.css139 рядків19 селекторів64 оголошення4 екрани
Анатомія

Розмітка звідси - та сама, що на product.html.

фото товаруПопулярне
на стенді фото не підставляється: його малює _nav.js лише всередині .pdp, і це саме та межа, яку цей крок знайшов
1Рамка - квадрат через aspect-ratio: 1, радіус 16, найбільший у системі. Всередині два градієнти: тінь під банкою знизу й дуже світлий вертикальний схил
2Фото - object-fit: contain і padding: 9%, тож банка не торкається країв. mix-blend-mode: multiply садить її на підкладку без білого прямокутника довкола
3Мітка - .gtag, чорна пігулка з зіркою через ::before. Абсолютна, 12 від лівого верхнього кута. Дві на весь продукт
4Мініатюри - 70×70, обрана бере рамку 2px --line-action, наведення піднімає на 2px. Це єдиний рух у компоненті
Що поїхало: .pmini

Половина файлу була не галереєю. .pmini - це липка панель на сторінці відгуків, яка тримає товар перед очима, поки ви читаєте чужі думки.

ЩоСкількиДе
.pmini та її правила9 правил, 1 екземплярproduct-reviews.html
з них перекладають .pcard5 із 9.pcard - 99 екземплярів на 17 екранах

Обгортка, яка існує щоб розмістити картку, це справа картки. Поїхало в product-card.css цілком - та сама форма, що .packlabel на 7.43 і .wfh-logo на 7.50: файл, у якому щось лежить, не є компонентом, яким воно є.

Здвоєний медіа-блок, якого не було
@media (min-width: 860px){ .pmini{ position: sticky; top: 120px; } } /* СТРУКТУРА, рядок 20 */ … @media (min-width:860px){ .pmini{ top: 88px; } } /* КОЛІР, рядок 50 */

Крок 7.55 прочитав це невірно, і 7.57 його виправив

Я побачив два блоки з однією умовою й назвав це здвоєним блоком - поламкою 7.46. Звів у один і видалив top: 120px, бо виграє 88. Читання було хибне. Перший блок стояв у секції структури, другий - у секції кольору. Це поділ етапу 08, який несе кожен файл системи, і про який заголовок кожного файлу каже вголос: «кольоровий блок нижче - той, що виграє, рівно так, як два стилевих листи стояли один на одному до поділу». Повторення тут - це запис поділу, а не випадковість. Обидва видання повернуті, у своєму порядку. Коштує це нічого: 88 виграє так само, як виграв до 7.55 і після.

Що з цього лишилось правдою: .pmini справді не галерея й справді поїхав у product-card.css. І вирішувач справді складав вердикти під ключем, що зливав два правила в одне - це виправлення теж лишається, воно окреме й корисне.

А потім статичний скан порахував усі такі пари в системі. Їх 74, і жодна з них не є поламкою:

Що цеСкількиЧи поламка
груповане правило, а потім правило для одного з його імен32ні - база й перекриття
секція структури, а потім секція кольору22ні - поділ етапу 08, навмисне
те саме значення написане двічі19мертве, але нічого не втрачається
жодне з переліченого1ця сторінка, і вона моя: обидва видання .pmini тепер лежать в одній секції чужого файлу

Тобто здвоєних медіа-блоків у системі немає жодного. Вісім «мертвих оголошень у трьох файлах», які я записав на 7.56 для auth-dialog, pdp-tabs і product-grid, - усі до одного той самий поділ структури й кольору. Скан бачив форму й не бачив причини; причину видно тільки читанням файлу.

Стан завантаження
.skpulse - клас скелета зі skeleton.css; цей файл дає йому лише ґрунт: --bg-sunken замість градієнтів, бо порожня рамка не має вдавати, що в ній щось стоїть
Товару немає в наявності
фото товару
ЩоЯк приглушеноЧому не однаково
велике фотоfilter: grayscale(.75) + opacity: .85це справжній img, фільтр на ньому нічого більше не зачіпає
мініатюриbackground-blend-mode: luminosityфото тут фон, і фільтр висушив би разом із ним помаранчеве кільце обраної

Обидві половини приїхали в цей файл: друга стоїть тут із кроку 3, першу 7.53 забрав із restock-note.css. Одне рішення - товар знебарвлюють, а не гасять - було написане у двох файлах, і той, хто змінив би одну половину, не побачив би другої.

Чому не сірий прямокутник

Знебарвлення на 75%, а не на 100, і прозорість 85, а не 50. Товар має лишитись упізнаваним: людина прийшла по конкретну банку і мусить бачити, що це вона. Чесно, а не драматично - voice.md 5.
Мініатюри малює не css

Вирішувач каже: чотири оголошення файлу не виграють ніде.

ОголошенняЗбігівХто виграє
.gal .gthumb{ background-image }32inline ×24 · .gthumb.skpulse ×8
{ background-position-x } · -y32те саме
{ background-size }32те саме

design/_nav.js:918 ставить кожній мініатюрі style.backgroundImage, backgroundSize і backgroundPosition - чотири різні кадри одного знімка, кожен зі своїм масштабом і зсувом. Css такого знати не може.

Не видаляю

background: var(--bg-page) center/74% no-repeat віддає п'ять значень, і js перебиває три. Два, що лишаються - колір ґрунту й no-repeat - виграють завжди. А center/74% це запас: те, як мініатюра виглядає, якщо скрипт не відпрацював. Та сама причина, з якої 7.49 лишив .emptybox без .mini, а 7.48 - .led-empty.
Стани

Шостий стан заведено на 8.16, і до нього рамка товару без знімка була порожнім квадратом із тінню під ним. Ось він живий:

Фото готуємо

мініатюр немає навмисно: якщо немає головного знімка, немає й кадрів із нього. Рядок малює .loadnote з empty-state.css - той самий атом, що несе «Завантажуємо товар…» на трьох екранах завантаження, і той самий час дієслова. Цей файл додає рівно одне оголошення - ґрунт: градієнти малюють предмет, що стоїть у світлі, а коли предмета немає, тінь падає від нічого. Слова й колір слів приносить сам атом
СтанЩо робитьЧому так
спокійбанка в рамці, чотири мініатюри під неюжодного руху: фото товару це факт, а не запрошення
обрана мініатюра.on: рамка 2px --line-action замість 1px волосиниєдине помаранчеве в компоненті, і воно означає «ви дивитесь на це»
наведення на мініатюрупідйом на 2px, перехід 150msнайменший рух, який видно. Курсор є тільки на десктопі, тож на телефоні стану немає взагалі
завантаження.skpulse: рамка й мініатюри стають --bg-sunkenградієнти зняті: порожня рамка не вдає, що в ній щось стоїть
немає в наявності.oosgal: фото знебарвлене, мініатюри злиті по яскравостідві техніки, бо одна з них висушила б помаранчеве кільце
фото немає 8.16:has(.loadnote): градієнти зняті, у рамці один рядок «Фото готуємо»у кольорі плейсхолдер «фото товару» не блідий, а невидимий - рамка має font-size: 0. Слова ставить атом, файл додає лише ґрунт
до 859 / від 860ширина min(100%, 430px) / 540px, мініатюри по центру на вузькомуна телефоні галерея - весь екран, на десктопі вона половина сітки й не має розповзатись
Правило і антиправило

Правило

Банка стоїть у світлі, а не на білому. Два градієнти й multiply дають тінь під дном і теплий схил згори. Товар за 2 000 грн на голому білому виглядає як картинка з прайсу, а не як річ, яку можна взяти в руки.

Антиправило

Не став на галерею більше однієї мітки. .gtag - дві на весь продукт. Три значки в кутку фото перетворюють товар на вітрину розпродажу, а принцип 4 каже: спокійно й упевнено, без терміновості.
Холостий контроль

Сторінка звіряє себе з файлом: 8 власних класів і 6 станів оголошено в gallery.css. Власних класів після 8.16 так само вісім: стан «фото немає» питає :has(.loadnote), тобто чужий клас усередині свого, і жодного нового імені в розмітку не заводить.

ПОВЕДІНКА НА ШИРИНІ - етап 10, крок 4

Від якого порогу: --bp-shell-wide, парою 859/860. Що саме міняється: нижче точки галерея тримається в min(100%, 430px) і центрує мініатюри, від точки розтягується до 540 і центрується сама. Обидва числа вже стояли на реєстрі; раунд молекул нічого не складав.
Рух

Робота: ВІДПОВІДЬ. Рухається: збільшення головного фото і рамка мініатюри. Тривалість --dur-fast (150ms), крива --ease-standard. Значення читаються через var(): підміна токена в браузері веде за собою кожне з них, тобто числа у файлі немає. При prefers-reduced-motion рух зникає, а стан лишається.

.25s на фото згорнулось у швидкий щабель: наведення на фото і наведення на мініатюру це одна робота.

Токени

Прочитані з gallery.css: 12 семантичних ролей і 10 примітивів.

Семантичні ролі (12)Примітиви (10)
--bg-photo --bg-photo-far --bg-photo-mark --bg-sunken --elevation-mark --fade-photo --line-action --line-hair --mark-faint --shadow-floor --text-action --text-photo-mark--fs-10 --fw-black --lh-flat --ls-caps --radius-8 --radius-16 --radius-pill --space-4 --space-8 --space-12
–--dur-fast --ease-standard

Повз шкалу: 70px (мініатюра - не щабель драбини розмірів), 12px двічі на позицію мітки, 9% і 74% (поля фото й масштаб кадру), 540px і 430px (дві максимальні ширини), 2px підйому, і п'ять відсотків усередині радіального градієнта. Це найбільш «відсотковий» файл системи, і по-іншому фото в рамці не поставити.

A/B

Три проходи по 40 екранах на чотирьох ширинах: HEAD, HEAD ще раз і робоче дерево. Нульовий прохід 0, різниця 0.

Дев'ять правил переїхали на 5 імпортів пізніше, здвоєний медіа-блок звівся в один, мертве top: 120px видалено - і жоден екран не намалювався інакше. Заміряно й напряму: .pmini до і після має ті самі position: sticky, top: 88px, ту саму сітку 116px 210px на 360 і той самий колір посилання.

Файл був п'ять медіа-блоків, став два. Одна точка перелому 860 була написана чотирма способами - з пробілом і без, у чотирьох місцях. Тепер її дві, і обидві однакові.

Немає в коді -> етап 09

Чого бракує

  • Точка перелому 860 повз набір 480 / 720 / 1180. Сьома в списку, разом із 419, 559, 620, 640, 820 і 859
  • Два способи сказати «квадрат» в одному файлі: aspect-ratio: 1 у рамці й 70px × 70px у мініатюрі
  • Колір рамки названо двічі тим самим токеном - border: 1px solid --line-hair у структурі й border-color: --line-hair у кольорі. Так побудовано весь поділ етапу 08, тож це рішення про угоду, а не про цей файл
  • Мініатюри не мають фокуса з клавіатури - _nav.js дає їм role="button" і aria-label, але не tabindex, тож перемкнути кадр можна лише мишею

Не домальовано наперед і не замовчано: етап 09 стартує з цього списку, зведеного в design/kit/docs/architecture.md.