/* ============================================================
   Блок «Сетка кейсов» (acf/cases-grid).
   ТЗ: site/docs/tz/tz-cases-grid.md

   Копия правил секции кейсов из темы (style.css) — пока главная рендерится
   старым шорткодом [bs-isotope-masonry-overlay], оба набора живы и совпадают
   один-в-один. КАК ТОЛЬКО контент главной переключат на блок — копию из
   themes/bootcommerce-child-main/style.css (раздел «СЕТКА КЕЙСОВ») удалить,
   чтобы источник правды остался один.

   ЧТО СЮДА СОЗНАТЕЛЬНО НЕ ПЕРЕНЕСЕНО:
   --case-pad / --case-max / --case-radius объявлены на :root в теме — их
   использует не только сетка, но и фиксированный переключатель темы и плашка
   hero на странице кейса. Дублировать числа здесь нельзя: разъедутся.
   ============================================================ */


.cases-section {
	--case-gap: clamp(0.75rem, 1.4vw, 1.75rem);
	/* Отступление от ТЗ: там --case-focus: var(--macro-accent) — заливочный
	   акцент проекта. Кольцо рисуется ВНУТРИ карточки, а обложки кейсов всегда
	   тёмно-синие: заливочный акцент на них не виден вообще (замер: #d33154
	   к скриму обложки #0e172b даёт 3.69). Берём «текстовую» светлоту акцента
	   в обеих темах + тёмный внутренний ореол, чтобы кольцо читалось и на
	   светлой фотообложке (Aukids).
	   ⚠️ История значения: было #9db8ff (светло-синий, 9.13 к скриму), затем
	   01.09.2026 — #00ccc0 (изумрудный, 8.84), с 07.09.2026 — #e78da1
	   (бордовая схема, 7.42 к скриму обложки #0e172b и 7.88 к скриму тёмной
	   темы #091020). Роль всё та же: цвет не должен зависеть ни от темы, ни от
	   палитры кейса.
	   ⚠️ 01.09.2026 хардкод заменён на токен --macro-accent-onmedia: он заведён
	   в style.css ровно под эту роль («акцент поверх произвольного медиа»),
	   объявлен на :root и НЕ переопределяется тёмной темой — то есть свойство
	   «не зависит ни от темы, ни от кейса» теперь держится объявлением токена,
	   а не дисциплиной. Хардкод здесь трогать не надо: значение приезжает
	   из style.css само. */
	--case-focus: var(--macro-accent-onmedia);
	--case-scrim: 14, 23, 43;

	/* Плашка под заголовком карточки. Цвет — тот же --case-scrim (в тёмной
	   теме он переопределяется, плашка едет за ним сама), здесь только сила
	   и высота нарастания. Разбор — ниже, у .case-card__rest-title::before. */
	--case-title-plate: 0.42;

	/* Плашка верхней строки карточки (номер «01» и хештеги). Цвет — тот же
	   --case-scrim, здесь только сила. Разбор и таблица замеров — ниже,
	   у .case-card__index::before / .case-card__tags::before.
	   ⚠️ ЕДИНСТВЕННАЯ ручка: не проходит контраст — крутить это число,
	   и больше ничего. */
	--case-tag-plate: 0.62;

	/* Отступ от верха. В ТЗ было clamp(4.5rem, 8vw, 8rem), нижняя граница
	   поднята до 5.75rem: при 4.5rem зазор до ленты контролов оставался
	   ~4-10px и читался как столкновение.
	   ⚠️ Пересчитано 22.08.2026, когда плавающую пилюлю заменили полосой во
	   всю ширину окна. Пилюля висела на --case-pad от верха, её кромка росла
	   вместе с шириной окна (на 1440 — 122.8px против 115.2px у clamp), и
	   второй аргумент max() реально перебивал первый: табы уходили под ленту.
	   Полоса прижата к верху, её кромка = --top-plate-space = 53/57px на любой
	   ширине (52/56 высоты контролов + 1px границы, вернувшейся 23.08.2026),
	   и максимум теперь ВСЕГДА берётся из clamp. Второй аргумент
	   оставлен страховкой: подрастут контролы — отступ подрастёт сам.
	   5.75rem при этом не снижаем обратно к 4.5rem: столкновения больше нет,
	   но эти 92px держат воздух над табами, а вертикальный ритм главной уже
	   на них построен.
	   +1.5rem — воздух между полосой и первым табом. */
	padding-top: max(clamp(5.75rem, 8vw, 8rem),
		calc(var(--top-plate-space) + 1.5rem));
	padding-bottom: clamp(3rem, 6vw, 6rem);
	padding-left: var(--case-pad);
	padding-right: var(--case-pad);
}

/* Full-bleed. По умолчанию секция уже во всю ширину (прямой ребёнок
   .entry-content). Если контент-редактор завернул шорткод в бутстраповский
   .container — вырываемся на 100vw; лишние пиксели под скроллбаром
   обрезает #page_entry_content { overflow-x: clip }. */
.container > .cases-section,
.container-fluid > .cases-section,
.row > * > .cases-section {
	width: 100vw;
	max-width: 100vw;
	margin-left: 50%;
	transform: translateX(-50%);
}

.cases-head,
.cases-grid {
	max-width: var(--case-max);
	margin-left: auto;
	margin-right: auto;
}


/* --- Шапка секции --- */

.cases-head {
	margin-bottom: clamp(1.75rem, 3.5vw, 3rem);
}

/* Логотип над заголовком — поле блока, по умолчанию ПУСТОЕ, и заполнять его на этом
   сайте не надо: портфолио обходится без логотипа в шапке секции, верхний воздух держит
   padding-top самой .cases-section. Правила ниже оставлены рабочими на случай, если поле
   когда-нибудь понадобится.
   [src$=".svg"] в селекторе обязателен: глобальная страховка
   img[src$=".svg"] { width:100%; height:auto } имеет специфичность (0,1,1)
   и иначе раздувает лого на всю ширину секции. */
.cases-head__logo,
.cases-head__logo[src$=".svg"] {
	display: block;
	height: 28px;
	width: auto;
	max-width: 100%;
	margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
}

.cases-head__title {
	font-size: clamp(2.75rem, 6.5vw, 6rem);
	font-weight: 700;
	line-height: 0.95;
	letter-spacing: -0.03em;
	color: var(--macro-ink);
	margin: 0;
}

.cases-head__lead {
	font-size: clamp(1rem, 1.15vw, 1.25rem);
	font-weight: 400;
	line-height: 1.5;
	max-width: 46ch;
	color: var(--macro-ink2);
	margin: 1rem 0 0;
}

.cases-head__rule {
	display: flex;
	align-items: center;
	gap: 1rem;
	margin-top: clamp(1.5rem, 3vw, 2.5rem);
}

.cases-head__rule::before {
	content: "";
	flex: 1 1 auto;
	height: 1px;
	background-color: var(--macro-border);
}

/* Счётчик можно выключить полем — тогда от линейки остаётся только hairline */
.cases-head__count {
	flex: 0 0 auto;
	font-size: 0.8125rem;
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: 0.12em;
	color: var(--macro-muted);
}

@media (min-width: 992px) {
	.cases-head {
		display: grid;
		grid-template-columns: repeat(12, 1fr);
		column-gap: var(--case-gap);
		align-items: start;
	}

	.cases-head__logo,
	.cases-head__logo[src$=".svg"] {
		height: 36px;
		grid-column: 1 / -1;
	}

	/* Правая колонка начинается на оптической линии заглавных h1 */
	.cases-head__lead {
		margin-top: 0.5rem;
	}

	.cases-head__rule {
		grid-column: 1 / -1;
	}
}


/* --- Грид --- */

.cases-grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--case-gap);
}

.case-card {
	position: relative;
	display: block;
	overflow: hidden;
	border-radius: var(--case-radius);
	aspect-ratio: var(--case-ratio, 3 / 2);
	background-color: var(--macro-brand-navy);
	box-shadow: 0 0.5rem 1.5rem rgba(var(--bs-dark-rgb), 0.18);
	touch-action: manipulation;
	transition: box-shadow 0.4s, border-color 0.4s, transform 0.25s ease;
}

/* Обложка. Перебиваем глобальную страховку по SVG (img[src$=".svg"]
   { width:100%; height:auto }) — иначе object-fit: cover не работает,
   а обложки кейсов как раз SVG. */
.case-card__img,
.case-card__img[src$=".svg"],
.cases-grid .case-card__img {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	max-width: none;
	object-fit: cover;
	object-position: center;
	filter: contrast(1.1) saturate(0.9);
	transition: transform 0.6s, filter 0.6s;
}

/* Фолбэк без обложки */
.case-card--noimg {
	background-color: var(--macro-brand-navy);
}

/* Заглушка для карточки без обложки. Раньше здесь стоял знак бренда (img),
   бренд с сайта убран — рисуем нейтральную диагональную штриховку целиком в CSS. */
.case-card__mark {
	position: absolute;
	inset: 0;
	display: block;
	opacity: 0.18;
	background-image: repeating-linear-gradient(
		135deg,
		currentColor 0,
		currentColor 1px,
		transparent 1px,
		transparent 14px
	);
	color: #ffffff;
}

/* Слой 2 — цветная подложка из ACF (цвет инлайном с !important, наш только opacity) */
.case-card__tint {
	position: absolute;
	inset: 0;
	display: block;
	background-color: var(--bs-primary);
}

/* Слой 3 — скрим под текстом покоя.

   Два градиента вместо одного (дополнение к ТЗ §5.1):
   1) виньетка в левом верхнем углу — под номер «01». Цветные подложки кейсов
      светлые (#fcfcfc, #f2f2f2, #fdf8f1, #f5f5f5, #fdf0ff), и белый номер на
      светлом углу обложки не читался вообще: замер по пикселям давал контраст
      1.02:1 на трёх карточках из пяти (в обеих темах). С виньеткой — 2.96:1
      в худшей точке и 3.55:1 в среднем. Виньетка локальная: за пределами
      угла обложка не темнеет;
   2) нижний скрим из ТЗ — под заголовок, без изменений. */
.case-card__scrim {
	position: absolute;
	inset: 0;
	display: block;
	z-index: 1;
	pointer-events: none;
	background:
		radial-gradient(
			220px 170px at 0% 0%,
			rgba(var(--case-scrim), 0.8),
			rgba(var(--case-scrim), 0) 75%
		),
		linear-gradient(
			to top,
			rgba(var(--case-scrim), 0.78) 0%,
			rgba(var(--case-scrim), 0.35) 38%,
			rgba(var(--case-scrim), 0) 68%
		);
	transition: opacity 0.5s;
}

/* Слой 4 — контент покоя */
.case-card__rest {
	position: absolute;
	inset: 0;
	z-index: 2;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	align-items: flex-start;
	text-align: left;
	padding: clamp(1.25rem, 2vw, 2.5rem);
	color: #fff;
}

/* --- Верхняя строка карточки: номер слева, хештеги справа ---
   ТЗ: site/docs/tz/tz-case-tags.md §2–§6.

   Верх .case-card__rest был занят наполовину (номер) и просился под вторую
   подпись: получается служебная строка «номер · теги» сверху и смысловая
   «название · анонс» снизу — два ясных регистра.

   width: 100 % обязателен: у .case-card__rest стоит align-items: flex-start,
   и без него строка сжалась бы по содержимому, а теги перестали бы уезжать
   к правому краю. */
.case-card__topline {
	display: flex;
	/* flex-start, а не center: при переносе тегов на вторую строку номер
	   обязан остаться на первой, а не уехать в вертикальный центр. */
	align-items: flex-start;
	justify-content: space-between;
	gap: 0.75rem;
	width: 100%;
}

.case-card__index {
	position: relative;   /* якорь плашки; на раскладку не влияет */
	/* при длинных тегах усыхать должна строка тегов, а не номер */
	flex: none;
	font-size: 0.8125rem;
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: 0.12em;
	/* ⚠️ Было rgba(255,255,255,.85), стало чистый #fff (01.09.2026).
	   Тот же вывод, что уже записан здесь про .65: тише строку делаем
	   КЕГЛЕМ И ТРЕКИНГОМ, а не прозрачностью — на светлых обложках номер
	   пропадал. Приглушение больше не нужно и вредно: под номером теперь
	   лежит плашка (ниже), и её сила посчитана для ЧИСТО БЕЛОГО текста. */
	color: #fff;
}

/* --- Хештеги ---
   Некликабельны намеренно (см. разбор в cases-grid.php): вся карточка —
   одна ссылка, архива у тегов нет.

   Перенос разрешён, обрезка запрещена: обрезать правоприжатую строку значит
   срезать НАЧАЛО слова слева, и это читается как баг. Длинный тег уедет
   на вторую строку, плашка вырастет вместе с ним.
   Внутри тега white-space: nowrap — сам тег не переносится по буквам никогда. */
.case-card__tags {
	position: relative;   /* якорь плашки */
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: 0.25rem 0.625rem;
	margin: 0;
	padding: 0;
	list-style: none;
	min-width: 0;
}

.case-card__tag {
	display: block;
	font-size: 0.75rem;
	font-weight: 500;
	line-height: 1.25;
	/* ⚠️ text-transform НЕ ставим: капс на кириллице съедает 15–20 % ширины
	   и хуже читается. Номер «01» капсом обходится — там две цифры.
	   Регистр задаёт словарь: аббревиатуры капсом (#CRM, #BI, #НКО),
	   обычные слова строчными (#краудфандинг). */
	letter-spacing: 0.06em;
	white-space: nowrap;
	/* Чистый #fff, приглушать альфой ЗАПРЕЩЕНО: строка мелкая (12px),
	   значит порог WCAG 1.4.3 — 4.5:1, а не 3.0, и вся сила плашки
	   посчитана именно под белый. */
	color: #fff;
}

/* Решётка тише самого слова — это разделитель, а не часть термина.
   Печатается PHP отдельным span'ом (не CSS-псевдоэлементом), потому что
   content:"" не выделяется мышью, не копируется и по-разному озвучивается. */
.case-card__tag-hash {
	color: rgba(255, 255, 255, 0.55);
}

/* --- Плашка верхней строки (слой 3.5, между скримом и текстом) ---

   ⚠️ ЗАЧЕМ. Верхний ЛЕВЫЙ угол затемнён виньеткой (см. .case-card__scrim
   выше), и она давала номеру 2.96 в худшей точке — ниже даже порога 3.0.
   Верхний ПРАВЫЙ угол не затемнён ничем, а обложки кейсов бывают почти
   белыми (#fcfcfc, #f2f2f2, #fdf8f1, #f5f5f5, #fdf0ff), и цветная подложка
   кейса лежит на них с opacity .25 — светлая обложка остаётся светлой.
   Белый текст на ней — это ~1.0.

   ⚠️ УРОК 01.09.2026, ради которого плашка устроена именно так: скрим,
   заданный в процентах ВЫСОТЫ КАРТОЧКИ, не защищает текст, который стоит
   не там, где скрим силён (разбор — у .case-card__rest-title::before, где
   заголовок рос вверх и уезжал из скрима). Поэтому здесь плашка привязана
   К САМОМУ БЛОКУ ТЕКСТА: перенеслись теги на вторую строку — плашка стала
   выше сама.

   ⚠️ ПОЧЕМУ ДВЕ ПЛАШКИ, А НЕ ОДНА НА ВСЮ .case-card__topline. Владелец
   01.09.2026 решил чинить провал номера «ТЕМ ЖЕ ПРИЁМОМ», что и теги.
   Одна плашка на всю строку — это сплошная тёмная полоса во всю ширину
   карточки, в том числе по СЕРЕДИНЕ обложки, где текста нет; платить
   потемневшей серединой у всех карточек ради двух углов — та же плохая
   сделка, из-за которой отказались усиливать общий скрим. Две локальные
   плашки дают тот же контраст и держат принцип «плашка появляется только
   там, где есть текст» (по этой же причине не зеркалили виньетку вправо).

   ⚠️ ГЕОМЕТРИЯ ВСЯ В em. Кегль тега меняется от карточки к карточке
   (обычная 12px / акцентная на десктопе 14px), а свесы и зоны растушёвки
   обязаны оставаться в тех же долях от буквы — иначе на акцентной плашка
   прижмётся к глифам. В px это пришлось бы вести медиазапросами.

   ⚠️ РАСТУШЁВКА ПО БОКАМ ОБЯЗАТЕЛЬНА: резкая кромка полупрозрачного
   прямоугольника на светлой обложке читается как артефакт вёрстки, причём
   заметнее самой проблемы с контрастом. Сверху и снизу растушёвку даёт
   фоновый градиент (крайние стопы), слева и справа — маска. Обе зоны
   лежат ЦЕЛИКОМ В СВЕСЕ (0.7em по бокам при свесе 0.85em, 0.4em сверху
   при свесе 0.5em), то есть до глифов не доходят и контраст не съедают.

   ⚠️ z-index: -1 кладёт плашку под текст, но НЕ ниже скрима: ближайший
   контекст наложения — .case-card__rest (z-index: 2), из него плашка
   не выпадает.

   Движок без mask-image просто проигнорирует объявление: останется плашка
   с резкими боками — некрасиво, но читаемо. Фолбэк не нужен. */
.case-card__index::before,
.case-card__tags::before {
	content: "";
	position: absolute;
	z-index: -1;
	inset: -0.5em -0.85em -0.4em -0.85em;
	pointer-events: none;
	/* ⚠️ СТОПЫ СНИЗУ ЗАДАНЫ В em ОТ КРАЯ (calc(100% - …)), А НЕ В ПРОЦЕНТАХ.
	   Замер 01.09.2026 показал, зачем: при стопах 70 % / 88 % зона затухания
	   заползала ВНУТРЬ строки тегов — плашка полной силы кончалась на 12-м
	   пикселе шестнадцатипиксельной строки, и нижние 3 пикселя глифов сидели
	   на alpha ≈ 0.4. Худший пиксель падал до 2.61 (dev-platform, 1440,
	   светлая) при пороге 4.5. Это ровно тот же класс ошибки, что и урок
	   с общим скримом: доля от ВЫСОТЫ БЛОКА не знает, где стоит текст.
	   Теперь обе зоны растушёвки лежат ЦЕЛИКОМ В СВЕСЕ (0.4em при свесе
	   0.5em сверху, 0.3em при свесе 0.4em снизу), то есть до глифов
	   не доходят и контраст не съедают. */
	background: linear-gradient(to bottom,
		rgba(var(--case-scrim), 0) 0,
		rgba(var(--case-scrim), var(--case-tag-plate)) 0.4em,
		rgba(var(--case-scrim), var(--case-tag-plate)) calc(100% - 0.3em),
		rgba(var(--case-scrim), 0) 100%);
	/* То же по горизонтали: 0.7em растушёвки при свесе 0.85em. */
	-webkit-mask-image: linear-gradient(to right,
		rgb(0 0 0 / 0) 0,
		rgb(0 0 0 / 1) 0.7em,
		rgb(0 0 0 / 1) calc(100% - 0.7em),
		rgb(0 0 0 / 0) 100%);
	mask-image: linear-gradient(to right,
		rgb(0 0 0 / 0) 0,
		rgb(0 0 0 / 1) 0.7em,
		rgb(0 0 0 / 1) calc(100% - 0.7em),
		rgb(0 0 0 / 0) 100%);
}

/* --- Сколько тегов видно ---
   Три уровня ограничения, каждый на своём месте: редакторское правило
   (2–3 тега, в описании поля), жёсткий потолок 4 в PHP и вот эта раскладка.
   display: none, а не visibility: лишний тег не должен читаться скринридером —
   на узком экране его нет ни для кого.

   Мобильная база: 2 у обычной карточки, 3 у акцентной 2×2 (у неё вдвое
   больше площади и свой, более крупный набор типографики). Дальше ступени
   только ДОБАВЛЯЮТ теги, поэтому обошлись min-width — max-width не нужен. */
.case-card__tag:nth-child(n + 3) {
	display: none;
}

.case-card--feature .case-card__tag:nth-child(3) {
	display: block;
}

@media (min-width: 576px) {
	.case-card__tag {
		font-size: 0.8125rem;
	}

	/* 576–991: три тега у всех карточек */
	.case-card__tag:nth-child(3) {
		display: block;
	}
}

@media (min-width: 992px) {
	/* ≥992: у акцентной 2×2 помещается четвёртый */
	.case-card--feature .case-card__tag {
		font-size: 0.875rem;
	}

	.case-card--feature .case-card__tag:nth-child(4) {
		display: block;
	}
}


.case-card__rest-title {
	font-size: clamp(1.25rem, 1.8vw, 1.875rem);
	font-weight: 700;
	line-height: 1.05;
	letter-spacing: -0.01em;
	max-width: 20ch;
	margin: 0;
	text-shadow: 0 2px 15px rgba(var(--bs-dark-rgb), 0.8);
	/* ⚠️ ЗДЕСЬ НЕЛЬЗЯ position (а также transform / filter / contain).
	   Разбор — у .case-card__rest-title-text ниже: h2 стоит МЕЖДУ
	   .case-card__rest и .case-card__link, и любой containing block на нём
	   схлопывает площадь нажатия stretched-link до коробки заголовка.
	   Якорь плашки живёт ВНУТРИ ссылки. */
}

.case-card--feature .case-card__rest-title {
	font-size: clamp(1.75rem, 3vw, 3.25rem);
	line-height: 1.02;
	max-width: 24ch;
}

/* --- Плашка под заголовком (слой 3.5, между скримом и текстом) ---

   ⚠️ ЗАЧЕМ. Замер композитом попиксельно (01.09.2026, 1440, обе темы,
   заголовок спрятан visibility:hidden, сэмпл — клиентский rect ПЕРВОЙ СТРОКИ,
   контраст каждого пикселя к белому):
     dev-platform  светлая 1.74 / тёмная 1.91   (худший пиксель)
     almacrm       светлая 2.30 / тёмная 2.68
     shagnsk       светлая 2.68 / тёмная 3.20
   При кегле 25.92 px / 700 порог 1.4.3 — 3.0. То есть три карточки из десяти
   не проходили AA, и владелец поставил худшую (dev-platform) ПЕРВОЙ.

   ⚠️ ПРИЧИНА — НЕ «слабый скрим», а РАССОГЛАСОВАНИЕ ЕГО ФОРМЫ С ТЕКСТОМ.
   Нижний скрим задан в процентах ВЫСОТЫ КАРТОЧКИ (0.78 у низа, 0.35 на 38 %,
   0 на 68 %) и рассчитан на короткий заголовок. Но заголовок прижат к низу и
   растёт ВВЕРХ: у dev-platform он в четыре строки, и его ПЕРВАЯ строка лежит
   на 48.8 % высоты карточки — там скрима уже почти нет (alpha ~0.22), под ней
   светлый скриншот интерфейса ~248/255. Замер геометрии: touchlink (2 строки)
   — первая строка на 29.5 %, alpha ~0.45, контраст 5.27 и вопросов нет;
   almacrm (3 строки) — 39.1 %; dev-platform (4 строки) — 48.8 %. Зависимость
   ровно от числа строк, а не от «плохой обложки»: обложка лишь проявляет.

   ⚠️ ПОЧЕМУ НЕ УСИЛИЛИ ОБЩИЙ СКРИМ. Чтобы вытянуть точку 48.8 %, стопы надо
   гнать примерно до 0.85 / 0.62 / 0.30 и растягивать до ~90 % высоты. Это
   платит ВСЕМИ десятью карточками (нижние две трети каждой обложки садятся
   на треть) ради двух проблемных. Обложка — смысловая часть карточки, а не
   фон; такую цену не берём.

   ⚠️ ПОЧЕМУ НЕ АДАПТИВНЫЙ СКРИМ ПО СВЕТЛОТЕ ОБЛОЖКИ. Он требует знать
   светлоту картинки — либо разбор канвасом на клиенте (ломается на lazy-load
   и без JS), либо доминирующий цвет с бэкенда (новое поле + пересчёт всей
   медиатеки). Отложено осознанно, не «забыто».

   ⚠️ ЧТО СДЕЛАНО ВМЕСТО. Плашка привязана К САМОМУ БЛОКУ ЗАГОЛОВКА, а не к
   высоте карточки: сколько строк — такая и плашка. Это и есть недостающая
   компенсация: чем выше уехал текст из скрима, тем больше площади он несёт
   с собой. Цена локальна — темнеет прямоугольник ПОД ТЕКСТОМ, остальная
   обложка не трогается вовсе.
   Побочно плашка почти бесплатна там, где и так хорошо: на тёмной обложке
   тёмно-синий поверх тёмно-синего почти не виден (touchlink 5.27 -> 6.60,
   на глаз разницы нет), а работу делает ровно на светлых.

   ⚠️ ГЕОМЕТРИЯ — ВСЯ В em, И ЭТО ПРИНЦИПИАЛЬНО. Кегль заголовка — clamp от
   ширины окна (20 -> 30 px), а свесы и зоны растушёвки обязаны оставаться
   в тех же долях от буквы, иначе на 375 плашка вылезет за padding карточки,
   а на 1440 прижмётся к глифам. В px это пришлось бы вести медиазапросами.

   ⚠️ ПОЧЕМУ z-index: -1. ::before — позиционированный элемент, без него он
   красится ПОВЕРХ строчного содержимого родителя, то есть поверх текста.
   Отрицательный z-index кладёт его под текст, но НЕ ниже скрима: ближайший
   контекст наложения — .case-card__rest (z-index: 2), из него плашка не
   выпадает. Проверяется глазом: плашка видна, текст на ней читается.

   ⚠️ РАСТУШЁВКА ОБЯЗАТЕЛЬНА ПО ВСЕМ ЧЕТЫРЁМ СТОРОНАМ. Резкая кромка
   полупрозрачного прямоугольника на светлой обложке читается как артефакт
   вёрстки («съехал блок»), причём заметнее, чем сама проблема с контрастом.
   Сверху и снизу растушёвку даёт фоновый градиент (крайние стопы), слева и
   справа — маска. Обе зоны растушёвки лежат ЦЕЛИКОМ В СВЕСЕ (0.9em по бокам,
   0.5em сверху при свесе 0.55em), то есть до глифов не доходят и контраст
   не съедают.

   ⚠️ Снизу плашка гаснет РАНЬШЕ, чем сверху (стопы 62 % / 86 % / 100 %), и
   это не симметрия ради симметрии: у низа карточки собственный скрим уже
   0.66, складывать с ним полную плашку — значит получить почти чёрную
   подложку под последней строкой. Компенсируем там, где дырка, а не везде.

   Движок без mask-image просто проигнорирует объявление: останется плашка
   с резкими боками — некрасиво, но читаемо. Фолбэк не нужен. */
/* --- Коробка заголовка внутри ссылки ---

   ⚠️ ЗАЧЕМ ЭТОТ ЭЛЕМЕНТ ВООБЩЕ ЕСТЬ. Плашке под заголовком нужен якорь
   (position: relative) ровно по коробке заголовка. Поставить его на сам <h2>
   нельзя, и это не вкусовщина, а замеренный регресс 01.09.2026:

     .case-card__rest (position: absolute; inset: 0)   <- задуманный якорь
       └ h2.case-card__rest-title
           └ a.case-card__link.stretched-link

   Бутстрап растягивает ::after ссылки на БЛИЖАЙШЕГО ПОЗИЦИОНИРОВАННОГО
   предка. Пока h2 статичен — это .case-card__rest, то есть вся карточка.
   Стоило поставить position: relative на h2 — предком стал он, и площадь
   нажатия схлопнулась до строки заголовка. Замер elementFromPoint по семи
   точкам каждой из 15 карточек (1440/768/375, обе темы, все четыре вкладки):
   мёртвыми были 5–7 точек из 7 у КАЖДОЙ карточки, площадь нажатия
   aukids 81x31 из 423x282 (2 %), istoki 132x31 из 1310x328 (1 %),
   dev-platform 662x95 из 867x585 (13 %). Владелец описал это как
   «как будто ссылки нет».

   Span решает задачу без компромисса: он ПОТОМОК ссылки, а не предок,
   поэтому на containing block её ::after не влияет никак, а коробку
   заголовка повторяет один в один — оба блочные и тянутся на ширину h2,
   геометрия плашки не изменилась ни на пиксель.

   ⚠️ ПРАВИЛО: между .case-card__rest и .case-card__link не должно появиться
   ни одного элемента с position / transform / filter / contain. Нужен якорь —
   ставь его внутрь ссылки, сюда. */
.case-card__link {
	display: block;
}

.case-card__rest-title-text {
	display: block;
	position: relative;   /* якорь плашки; на раскладку не влияет */
}

.case-card__rest-title-text::before {
	content: "";
	position: absolute;
	z-index: -1;
	inset: -0.55em -0.9em -0.35em -0.9em;
	pointer-events: none;
	background: linear-gradient(to bottom,
		rgba(var(--case-scrim), 0) 0,
		rgba(var(--case-scrim), var(--case-title-plate)) 0.5em,
		rgba(var(--case-scrim), var(--case-title-plate)) 62%,
		rgba(var(--case-scrim), calc(var(--case-title-plate) * 0.5)) 86%,
		rgba(var(--case-scrim), 0) 100%);
	-webkit-mask-image: linear-gradient(to right,
		rgb(0 0 0 / 0) 0,
		rgb(0 0 0 / 1) 0.9em,
		rgb(0 0 0 / 1) calc(100% - 0.9em),
		rgb(0 0 0 / 0) 100%);
	mask-image: linear-gradient(to right,
		rgb(0 0 0 / 0) 0,
		rgb(0 0 0 / 1) 0.9em,
		rgb(0 0 0 / 1) calc(100% - 0.9em),
		rgb(0 0 0 / 0) 100%);
}

.case-card__link,
.case-card__link:hover,
.case-card__link:focus {
	color: #fff;
	text-decoration: none;
}

.case-card__meta {
	margin-top: 0.75rem;
	max-width: 46ch;
}

.case-card__meta-excerpt {
	font-size: 0.875rem;
	line-height: 1.45;
	color: rgba(255, 255, 255, 0.82);
	margin: 0 0 0.5rem;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.case-card__meta-cta {
	display: inline-block;
	font-size: 0.8125rem;
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: #fff;
}

/* Слой 5 — оверлей (визуальный дубль, aria-hidden) */
.case-card__overlay {
	position: absolute;
	inset: 0;
	z-index: 3;
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;
	padding: clamp(1.25rem, 2vw, 2.5rem);
	opacity: 0;
	pointer-events: none;
	transition: opacity 500ms;
}

.case-card__overlay .overlay_background {
	background-color: var(--bs-primary);
}

.case-card__overlay-content {
	position: relative;
	z-index: 2;
	max-width: 46ch;
	color: #fff;
}

/* Копия тегов в оверлее: кикером НАД заголовком. Плашки тут нет и не нужно —
   оверлей залит цветом кейса, фон плотный, и приглушение альфой здесь как раз
   законно (в покое оно запрещено, см. .case-card__tag).
   Разделитель между тегами — гаттер флекса, а не символ в разметке: точка
   переносилась бы отдельным «словом» и повисала в начале строки. */
.case-card__overlay-tags {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.125rem 0.75rem;
	margin: 0 0 0.625rem;
	font-size: 0.75rem;
	font-weight: 500;
	letter-spacing: 0.06em;
	line-height: 1.25;
	color: rgba(255, 255, 255, 0.8);
}

.case-card__overlay-tag {
	white-space: nowrap;
}

.case-card--feature .case-card__overlay-tags {
	font-size: 0.8125rem;
	margin-bottom: 0.875rem;
}

.case-card__title {
	display: block;
	font-size: clamp(1.125rem, 1.4vw, 1.5rem);
	font-weight: 600;
	line-height: 1.15;
	text-shadow: 0 2px 12px rgba(var(--bs-dark-rgb), 0.65);
}

.case-card--feature .case-card__title {
	font-size: clamp(1.375rem, 2vw, 2rem);
}

.case-card__excerpt {
	margin-top: 0.75rem;
	font-size: 0.9375rem;
	line-height: 1.5;
	color: rgba(255, 255, 255, 0.88);
	display: -webkit-box;
	-webkit-line-clamp: 4;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.case-card--feature .case-card__excerpt {
	-webkit-line-clamp: 7;
}

.case-card__cta {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	margin-top: 1.25rem;
	padding: 0.5rem 1.25rem;
	font-size: 0.8125rem;
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: #fff;
	border: 2px solid rgba(255, 255, 255, 0.9);
	border-radius: 999px;
}

.case-card__cta i,
.case-card__meta-cta i {
	transition: transform 0.3s;
}


/* --- Раскрытие оверлея: hover (только точный указатель) и КЛАВИАТУРНЫЙ фокус ---

   Было :focus-within, и это давало баг: при обычном нажатии (мышь/тап) ссылка
   получает фокус, карточка мгновенно заливалась цветным оверлеем целиком —
   «стрёмная» вспышка вместо отклика на нажатие. Теперь раскрытие только по
   :focus-visible, то есть по клавиатуре: у указателя браузер его не ставит.
   Отклик на нажатие — отдельный, лёгкий (см. ниже .case-card:active). */

/* ⚠️ БЫЛО `.case-card__rest > .case-card__index` — ПРЯМОЙ потомок. После
   обёртки номера и тегов в .case-card__topline (01.09.2026, ТЗ tz-case-tags.md
   §3.2) номер перестал быть прямым потомком .case-card__rest, и правило молча
   умерло бы: при клавиатурном фокусе номер повис бы поверх раскрытого оверлея.
   Гасим строку ЦЕЛИКОМ — теги обязаны исчезать вместе с номером. */
.case-card:has(.case-card__link:focus-visible) .case-card__topline,
.case-card:has(.case-card__link:focus-visible) .case-card__meta,
.case-card:has(.case-card__link:focus-visible) .case-card__tint,
.case-card:has(.case-card__link:focus-visible) .case-card__scrim {
	opacity: 0;
}

.case-card:has(.case-card__link:focus-visible) .case-card__overlay {
	opacity: 1;
}


/* --- Отклик на нажатие ---
   Лёгкое плавное затемнение и еле заметное «проседание». Обложка и заголовок
   остаются видны, заливки цветом нет. Работает одинаково на мыши и на тапе:
   :active срабатывает в обеих ветках, а системную синюю подсветку тапа гасим. */

.case-card {
	-webkit-tap-highlight-color: transparent;
}

.case-card::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 4;
	border-radius: inherit;
	background-color: rgba(var(--case-scrim), 1);
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.25s ease;
}

.case-card:active::after {
	opacity: 0.18;
}

.case-card:active {
	transform: scale(0.995);
}

@media (prefers-reduced-motion: reduce) {
	.case-card::after {
		transition: none;
	}

	.case-card:active {
		transform: none;
	}
}

@media (hover: hover) and (pointer: fine) {
	/* на десктопе за анонс отвечает оверлей */
	.case-card__meta {
		display: none;
	}

	.case-card:hover .case-card__rest,
	.case-card:hover .case-card__tint,
	.case-card:hover .case-card__scrim {
		opacity: 0;
	}

	.case-card:hover .case-card__overlay {
		opacity: 1;
	}

	.case-card:hover .case-card__img {
		filter: contrast(1) saturate(1);
		transform: scale(1.03);
	}

	/* --- Отклик карточки на мышь: ТЕНЬ, НИКАКОГО КОНТУРА ---
	   ⚠️ Владелец 07.09.2026: «убери красный контур при наведении на карточку
	   портфолио». Убрано ДВА следа акцента, оба появились с перекраской схемы
	   в бордо:
	     • здесь — тень была подкрашена акцентом (--bs-primary-rgb, замер:
	       rgba(139,29,53,.28)), на белом фоне 32px размытия читались красноватым
	       ореолом по контуру карточки. Тень оставлена (это подъём, а не контур),
	       но цвет её теперь НЕЙТРАЛЬНЫЙ — тот же --bs-dark-rgb, что и в покое,
	       только гуще и дальше;
	     • в тёмной теме — акцентная РАМКА 1px на hover (см. блок «Тёмная тема»).
	   Ни на клавиатурный фокус, ни на раскрытие оверлея это не влияет: кольцо
	   фокуса рисует ::after самой карточки, и оно осталось как было. */
	.case-card:hover {
		box-shadow: 0 1rem 2rem rgba(var(--bs-dark-rgb), 0.28);
	}

	.case-card:hover .case-card__cta i {
		transform: translateX(4px);
	}

	/* клавиатура: тот же раскрытый вид, что и на hover */
	.case-card:has(.case-card__link:focus-visible) .case-card__rest {
		opacity: 0;
	}
}

/* ⚠️ .case-card__topline здесь ОБЯЗАТЕЛЕН: на hover он гаснет вместе
   с родителем .case-card__rest и своей длительности не требует, но при
   КЛАВИАТУРНОМ фокусе гасится отдельным правилом (см. выше) — без своего
   transition строка тегов исчезала бы мгновенно, а заголовок плавно. */
.case-card__rest,
.case-card__topline,
.case-card__tint {
	transition: opacity 0.5s;
}

/* Кольцо фокуса у карточки — единственное место в проекте, где внешнее
   кольцо отменяется вручную. Причин две:
     1. у карточки overflow: hidden — внешний outline срезался бы;
     2. по фокусу раскрывается оверлей (z-index 3) и накрывает собой всё
        содержимое карточки, включая ссылку с её псевдоэлементом.
   Поэтому кольцо рисует псевдоэлемент САМОЙ карточки на слое выше оверлея
   и выше скрима нажатия (::after, z-index 4), а сама ссылка кольца не имеет.

   !important обязателен: общее кольцо раздаётся правилом
   `:focus-visible { outline: … !important }` в теме (style.css, раздел
   «ФОКУС С КЛАВИАТУРЫ», им снята глобальная заглушка родителя), обычным
   правилом его не отменить. */
.case-card__link:focus-visible,
.case-card__link:focus:not(:focus-visible) {
	outline: none !important;
}

/* Цвет обложки и оверлея у каждого кейса свой (ACF, от охристого до
   индиго), поэтому кольцо двухслойное: светлый контур + тёмный внутренний
   ореол. Такая пара читается и на светлой заливке, и на тёмной. */
.case-card:has(.case-card__link:focus-visible)::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 5;
	border-radius: inherit;
	outline: 3px solid var(--case-focus);
	outline-offset: -3px;
	box-shadow: inset 0 0 0 6px rgba(9, 16, 32, 0.55);
	pointer-events: none;
}

/* Без :has() оверлей по фокусу не раскрывается (правило выше тоже на :has),
   поэтому там кольцо можно оставить на псевдоэлементе самой ссылки. */
@supports not selector(:has(*)) {
	.case-card__link:focus-visible::after {
		content: "";
		position: absolute;
		inset: 0;
		z-index: 4;
		border-radius: inherit;
		outline: 3px solid var(--case-focus);
		outline-offset: -3px;
		box-shadow: inset 0 0 0 6px rgba(9, 16, 32, 0.55);
		pointer-events: none;
	}
}


/* --- Тач-устройства: оверлея нет, карточка закончена в покое --- */

@media (hover: none), (pointer: coarse) {
	.case-card__overlay {
		display: none;
	}

	/* на тач-устройствах карточка всегда в состоянии покоя: оверлея нет,
	   фокус после тапа ничего не раскрывает */
	/* ⚠️ .case-card__topline тут ОБЯЗАТЕЛЕН отдельной строкой: у него своя
	   opacity: 0 из правила по фокусу выше, и возврат родителя
	   (.case-card__rest) её не отменяет — прозрачности перемножаются.
	   Без этой строки на тач-устройстве после тапа номер и теги пропадали бы. */
	.case-card:has(.case-card__link:focus-visible) .case-card__rest,
	.case-card:has(.case-card__link:focus-visible) .case-card__topline,
	.case-card:has(.case-card__link:focus-visible) .case-card__tint,
	.case-card:has(.case-card__link:focus-visible) .case-card__scrim,
	.case-card:has(.case-card__link:focus-visible) .case-card__meta {
		opacity: 1;
	}

	.case-card__meta-cta {
		padding: 0;
		border: 0;
	}
}


/* --- Брейкпоинты сетки ---

   Посадка карточек (акцентная 2×2, хвост ряда, одиночка, пара) задаётся
   КЛАССАМИ-модификаторами, а не :nth-child. Причина — фильтр по категориям:
   скрытая display:none карточка всё равно считается в :nth-child, и раскладка
   бы поехала. Классы считает PHP при рендере (lazarewww_case_layout_classes)
   и пересчитывает JS после переключения таба — по ВИДИМЫМ карточкам. */

/* Отфильтрованные карточки */
.case-card.is-hidden {
	display: none;
}

/* < 768: одна колонка, всё 3:2, акцент не выделяется размером */
.case-card,
.case-card--feature {
	--case-ratio: 3 / 2;
	grid-column: auto;
	grid-row: auto;
}

@media (min-width: 768px) and (max-width: 991.98px) {
	.cases-grid {
		grid-template-columns: repeat(6, 1fr);
	}

	.case-card {
		--case-ratio: 3 / 2;
		grid-column: span 3;
	}

	.case-card--feature {
		grid-column: span 6;
		grid-row: auto;
		--case-ratio: 3 / 2;
	}

	/* последняя осталась одна в ряду */
	.case-card--tail2 {
		grid-column: span 6;
		--case-ratio: 5 / 2;
	}

	.case-card--solo {
		grid-column: span 6;
		--case-ratio: 3 / 2;
	}
}

@media (min-width: 992px) {
	.cases-grid {
		grid-template-columns: repeat(12, 1fr);
	}

	.case-card {
		--case-ratio: 3 / 2;
		grid-column: span 4;
	}

	.case-card--feature {
		grid-column: span 8;
		grid-row: span 2;
		aspect-ratio: auto;
	}

	/* хвост: последняя одна в ряду (N = 4, 7, 10 …) */
	.case-card--tail3-1 {
		grid-column: span 12;
		--case-ratio: 4 / 1;
	}

	/* хвост: последних две (N = 5, 8, 11 …) */
	.case-card--tail3-2 {
		grid-column: span 6;
		--case-ratio: 2 / 1;
	}

	/* ровно две карточки — по половине ширины */
	.case-card--pair {
		grid-column: span 6;
		--case-ratio: 3 / 2;
	}

	/* Единственный кейс: акцентной «фичи» 2×2 он не получает (её дают только при
	   4+ карточках), но и полосой 4:1 быть не должен. Берём всю ширину контейнера
	   (пустой колонки справа не остаётся) с киногеничным 21:9 и потолком по высоте,
	   чтобы на больших экранах карточка не съедала весь первый экран. */
	.case-card--solo {
		grid-column: 1 / -1;
		/* width обязателен: с aspect-ratio + max-height грид-элемент иначе
		   пересчитывает ШИРИНУ от предельной высоты и оставляет дыру справа. */
		width: 100%;
		--case-ratio: 21 / 9;
		max-height: min(70vh, 780px);
	}

	.case-card--feature .case-card__rest {
		padding: clamp(1.75rem, 2.5vw, 3rem);
	}
}

/* ОТКАТ (включить модификатор на .cases-grid):
   возвращает заголовок покоя в центр карточки, как было до переверстки.
   .cases-grid--centered .case-card__rest {
       align-items: center; justify-content: center; text-align: center;
   }
   .cases-grid--centered .case-card__index { display: none; }
   ОТКАТ радиуса: .cases-section { --case-radius: 2.25rem; }
   ОТКАТ широкой плашки для 7-й карточки:
   .case-card:last-child:nth-child(3n+1):not(.case-card--feature) {
       grid-column: span 4; --case-ratio: 3/2;
   }
*/


/* --- Тёмная тема --- */

.dark-theme .cases-section {
	/* --case-focus одинаков в обеих темах, см. комментарий выше */
	--case-scrim: 9, 16, 32;
}

/* Заголовок карточки всегда белый: общий .dark-theme a{...accent-text}
   имеет специфичность выше, чем .case-card__link, и красил заголовки
   в голубой поверх обложки. */
.dark-theme .case-card__link,
.dark-theme .case-card__link:hover,
.dark-theme .case-card__link:focus {
	color: #fff;
}

.dark-theme .case-card {
	background-color: var(--macro-surface);
	border: 1px solid var(--macro-border);
	box-shadow: none;
}

.dark-theme .case-card__tint {
	opacity: 0.18;
}

.dark-theme .case-card__scrim {
	background:
		radial-gradient(
			220px 170px at 0% 0%,
			rgba(var(--case-scrim), 0.8),
			rgba(var(--case-scrim), 0) 75%
		),
		linear-gradient(
			to top,
			rgba(var(--case-scrim), 0.85) 0%,
			rgba(var(--case-scrim), 0.4) 38%,
			rgba(var(--case-scrim), 0) 68%
		);
}

@media (hover: hover) and (pointer: fine) {
	/* ⚠️ border-color: var(--macro-accent) здесь БЫЛО и снято 07.09.2026 по
	   требованию владельца: в тёмной теме карточка несёт рамку 1px
	   --macro-border, и на hover она перекрашивалась в акцент — замер до
	   правки: #60293c -> #d33154, то есть ровно тот «красный контур».
	   Рамка остаётся рамкой покоя, отклик держит тень. Не возвращать. */
	.dark-theme .case-card:hover {
		box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
	}
}


/* --- Появление при скролле ---
   Класс .reveal-ready вешает JS: без скрипта сетка просто видна.
   Задержки по умолчанию НЕТ: карточка проявляется сразу, как только наблюдатель
   до неё дошёл (а он срабатывает с запасом до входа в кадр, см. cases-grid.js).
   Шахматка — только на первом экране, класс .is-stagger; ниже она копилась бы,
   и карточка «догоняла» бы пользователя уже после того, как он долистал. */

.cases-grid.reveal-ready .case-card {
	opacity: 0;
	transform: translateY(16px);
	transition: opacity 0.45s cubic-bezier(0.22, 0.61, 0.36, 1),
		transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1),
		box-shadow 0.4s, border-color 0.4s;
	transition-delay: 0s;
}

/* первый экран: лёгкая шахматка, потолок — 3 шага, дальше не растёт */
.cases-grid.reveal-ready .case-card.is-stagger {
	transition-delay: calc(min(var(--i, 0), 3) * 60ms);
}

.cases-grid.reveal-ready .case-card.is-inview {
	opacity: 1;
	transform: none;
}

/* Мгновенный показ (быстрый флик, прыжок по якорю, переключение таба): карточка
   уже в кадре, анимировать её — значит показать дырку. JS снимает класс через
   кадр, иначе карточка навсегда осталась бы без ховер-переходов. */
.cases-grid.reveal-ready .case-card.is-instant {
	transition: none;
}

@media (prefers-reduced-motion: reduce) {
	.cases-grid.reveal-ready .case-card,
	.cases-grid.reveal-ready .case-card.is-stagger,
	.cases-grid.reveal-ready .case-card.is-inview {
		opacity: 1;
		transform: none;
		transition: none;
		transition-delay: 0s;
	}

	.case-card__img,
	.case-card:hover .case-card__img {
		transform: none;
		transition: none;
	}
}



/* --- Табы категорий ---
   Стоят выше шапки секции, по той же левой линии --case-pad, что заголовок
   и первая карточка. Лента с горизонтальным скроллом: на 375 три-четыре таба
   в строку не влезают, а перенос рвёт ритм шапки. */

/* Меру и центровку держит обёртка, а не сама лента: лента скроллится
   (overflow-x), а значит обрезает всё, что вылезает за её край, — включая
   кольцо фокуса у крайних табов. Поэтому лента получает padding под кольцо
   и гасит его отрицательным margin: видимая геометрия табов совпадает с
   обёрткой на любой ширине, а места под кольцо хватает. */
.cases-tabs-wrap {
	max-width: var(--case-max);
	margin: 0 auto clamp(1.25rem, 2.5vw, 2rem);
}

.cases-tabs {
	display: flex;
	flex-wrap: nowrap;
	gap: 0.5rem;
	padding: var(--case-focus-gutter, 6px);
	margin: calc(-1 * var(--case-focus-gutter, 6px));
	overflow-x: auto;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}

.cases-tabs::-webkit-scrollbar {
	display: none;
}

/* --- Подсказка прокрутки: затухание у края ленты ---
   ⚠️ ЗАЧЕМ. На 320–375 из трёх вкладок целиком видна ОДНА (замерено
   01.09.2026: переполнение ленты 332 px на 320 и 279 px на 375). Полоса
   листается, но об этом ничто не сообщало — скроллбар погашен выше
   намеренно. Затухающий край и есть сообщение «тут есть продолжение».
   ⚠️ Уменьшением кегля это НЕ лечится: 19->17/15 px срезало переполнение
   с 396 до 332, а видно по-прежнему одну. Не пытаться снова.

   ⚠️ ФЕЙД ОБЯЗАН БЫТЬ ЧЕСТНЫМ. Затухание с той стороны, где ничего нет, —
   не украшение, а ложь: оно обещает продолжение и заставляет тянуть ленту
   в пустоту. Поэтому ширина затухания с каждой стороны своя и правится
   из JS (см. cases-grid.js, initTabsFade): класс появляется только тогда,
   когда с этой стороны реально есть скрытый контент.
   Дошли до конца — правый край гаснуть перестаёт.

   ⚠️ ПОЭТОМУ ЖЕ ТУТ НЕТ МЕДИАЗАПРОСА. Условие — переполнение, а не ширина
   экрана: на 768+ лента влезает целиком (3 из 3, переполнение 0), классов
   никто не ставит, и фейда нет сам собой. Появится длинная подпись — фейд
   включится и на десктопе, где он и нужен. Брейкпоинт тут врал бы в обе
   стороны.

   ⚠️ Маска — на САМОЙ ленте, и это осознанно ИНАЧЕ, чем у кромки хедера
   (там фон вынесен в ::before, чтобы маска не задела контент). Здесь
   затухать должны как раз сами вкладки у края — иначе подсказки не выйдет.
   На кликабельность это не влияет: маска — стадия отрисовки, попадание
   курсора считается по геометрии. Проверено реальным кликом по последней
   вкладке под затуханием (отчёт frontender 01.09.2026).

   ⚠️ Стопы по 4 на сторону, а не по 2. Линейный переход alpha 1 -> 0 даёт
   видимую полосу Маха на середине градиента — тот же эффект, что ловили
   на кромке хедера. Не упрощать.

   Фолбэк не нужен и не заводится: движок без mask-image просто игнорирует
   объявление — лента остаётся как была, ничего не обрезается и не ломается,
   теряется только подсказка. Именно поэтому здесь mask и не задаёт
   геометрию: ни свеса, ни отступов на ней не висит.

   При обеих переменных в 0 стопы схлопываются в одну точку, и маска
   становится сплошной — это и есть состояние «фейда нет». */
.cases-tabs {
	--tabs-fade-w: 32px;
	--tabs-fade-start: 0px;
	--tabs-fade-end: 0px;

	-webkit-mask-image: linear-gradient(to right,
		rgb(0 0 0 / 0)    0,
		rgb(0 0 0 / 0.35) calc(var(--tabs-fade-start) * 0.45),
		rgb(0 0 0 / 0.85) calc(var(--tabs-fade-start) * 0.80),
		rgb(0 0 0 / 1)    var(--tabs-fade-start),
		rgb(0 0 0 / 1)    calc(100% - var(--tabs-fade-end)),
		rgb(0 0 0 / 0.85) calc(100% - var(--tabs-fade-end) * 0.80),
		rgb(0 0 0 / 0.35) calc(100% - var(--tabs-fade-end) * 0.45),
		rgb(0 0 0 / 0)    100%);
	mask-image: linear-gradient(to right,
		rgb(0 0 0 / 0)    0,
		rgb(0 0 0 / 0.35) calc(var(--tabs-fade-start) * 0.45),
		rgb(0 0 0 / 0.85) calc(var(--tabs-fade-start) * 0.80),
		rgb(0 0 0 / 1)    var(--tabs-fade-start),
		rgb(0 0 0 / 1)    calc(100% - var(--tabs-fade-end)),
		rgb(0 0 0 / 0.85) calc(100% - var(--tabs-fade-end) * 0.80),
		rgb(0 0 0 / 0.35) calc(100% - var(--tabs-fade-end) * 0.45),
		rgb(0 0 0 / 0)    100%);
}

/* Классы ставит JS по фактической позиции прокрутки. Их отсутствие —
   штатное состояние (лента влезла целиком или JS не отработал). */
.cases-tabs.is-fade-start {
	--tabs-fade-start: var(--tabs-fade-w);
}

.cases-tabs.is-fade-end {
	--tabs-fade-end: var(--tabs-fade-w);
}

/* ⚠️ Кегль вкладок УМЕНЬШЕН 01.09.2026 по решению владельца: 19 px от 768 px
   читались великовато. Стало 15 px до 768 и 17 px от 768 (было 17 и 19).
   Вес не тронут: 600 у обычной, 700 у активной.

   ⚠️ ЧТО ЭТО СТОИТ ПО WCAG — читать перед тем, как трогать кегль снова.
   Подпись АКТИВНОЙ вкладки — белый на заливке акцента. Замер (живьём,
   composited-пиксели, 07.09.2026, бордовая схема): светлая тема 9.03,
   тёмная 4.84. Контраст от кегля НЕ зависит — меняется только ПОРОГ,
   который к нему применяют:
     19 px / 700  → «крупный текст» по 1.4.3, порог 3.0, запас 61 %
     17 px / 600  → обычный текст,            порог 4.5, запас 7.6 %
   ⚠️ ЭТИ ЧИСЛА ОБНОВЛЕНЫ 07.09.2026 вместе со сменой акцента на бордовый.
   На изумруде здесь стояло 4.57 и запас 1.6 % — самая узкая цифра всей
   схемы, и абзац ниже требовал перемерять её первым делом при смене акцента.
   Перемеряно: бордовая заливка тёмной темы #d33154 даёт белому 4.84, то есть
   запас вырос в 4,75 раза. Причина — не подбор, а то, что тёмная рампа
   опущена на ступень (поверхность #142f30 → #3e1b27), см. ТЗ §2.4.
   ⚠️ Правило остаётся в силе: любое движение --macro-accent в тёмной теме
   в сторону светлее съедает этот запас. Меняешь акцент — перемеряй
   пару «белый ↔ заливка активной вкладки» первым делом.
   ⚠️ Окно светлоты заливки в тёмной теме теперь ШИРИНОЙ 0.0247 по Y
   (было 0.0115), разбор — style.css, «ТЁМНАЯ ТЕМА». Заливка #d33154 стоит
   на 33 % его глубины, а не впритык к потолку, как стоял изумруд.
   ⚠️ Вариант «затемнить заливку до пола окна» пересчитан: #d22d51 даёт белому
   4.96 (+0.12), отрыв от поверхности падает с 3.12 до 3.04. Технически он
   больше не запрещён (на изумруде падал ровно на 3.00), но и не нужен —
   запас 7.6 % уже есть. Не переоткрывать без причины.
   ⚠️ Отрыв заливки от поверхности — 3.12 при пороге 3.0, запас 4 %. Это
   ВТОРАЯ по узости цифра схемы; поверхность тёмной темы нельзя осветлять
   выше Y = 0.022285 (#3e1b27 = 0.019545, запас 14 %).
   ⚠️ Обратный ход (вернуть 19/700 ради порога 3.0) — правка ДВУХ чисел ниже,
   но он уже отклонён владельцем визуально. Не возвращать «ради WCAG»
   без разговора: соответствие AA есть и на нынешнем кегле. */
.cases-tab {
	flex: 0 0 auto;
	padding: 0.625rem 1.125rem;
	/* Пилюля — КОНТРОЛ, а не декоративная линейка: 1.4.11 к её границе
	   применяется. --macro-border давал 1.27 к белому (замер) — пилюля была
	   практически невидима. --macro-line-strong даёт 3.90 / 6.39
	   (перемерено 07.09.2026: #937b81 к белому, #ae939a к фону тёмной темы). */
	border: 1px solid var(--macro-line-strong);
	border-radius: 999px;
	background-color: transparent;
	color: var(--macro-ink2);
	font-family: inherit;
	/* 15 px. Мелкий кегль тут работает ещё и на прокрутку ленты: на 320–375
	   вкладок теперь ТРИ, и от их ширины зависит, сколько видно без свайпа
	   (замер до/после — в отчёте frontender 01.09.2026). */
	font-size: 0.9375rem;
	/* ⚠️ Вариант B, выбран владельцем по листам 01.09.2026 из трёх (A 600/700,
	   B 500/600, C 400/600). Было 600/700. A читалась тяжело — три плотных
	   пилюли подряд на 1440; C выхолащивала неактивные (400 на светлом фоне
	   блёкло) и слишком резко отрывала активную. B держит и опрятность,
	   и иерархию.
	   ⚠️ На контраст вес НЕ влияет: замерено 8.45 / 4.57 одинаково у всех
	   трёх вариантов (24 замера). Порог остаётся 4.5 — см. блок выше. */
	font-weight: 500;
	line-height: 1.2;
	letter-spacing: 0.015em;
	white-space: nowrap;
	cursor: pointer;
	touch-action: manipulation;
	transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}

@media (hover: hover) and (pointer: fine) {
	.cases-tab:hover {
		border-color: var(--macro-accent);
		color: var(--macro-ink);
	}
}

.cases-tab:focus-visible {
	/* кольцо — из общей системы фокуса темы (--ctx-accent). Отступ поджат до
	   2px: лента табов скроллится по горизонтали (overflow-x: auto), и на
	   3px кольцо крайнего таба подрезалось краем ленты. */
	outline-offset: 2px;
}

.cases-tab.is-active {
	background-color: var(--macro-accent);
	border-color: var(--macro-accent);
	color: var(--macro-accent-ink);
	/* 600 (вариант B). Роль у веса теперь ЧИСТО маркирующая. Раньше 700 был
	   условием «крупного текста» по 1.4.3 (порог 3.0) в паре с кеглем 19 px;
	   после уменьшения кегля до 17 px порог всё равно 4.5, и вес на
	   соответствие не влияет вовсе — он просто отделяет активную вкладку
	   от остальных. Шаг ровно в одну ступень (500 -> 600) взят намеренно:
	   при бОльшем разрыве активная заметно шире неактивной, и лента дёргается
	   на каждом переключении. */
	font-weight: 600;
}

@media (min-width: 768px) {
	.cases-tab {
		/* 17 px — ориентир основного текста страницы, а не заголовка.
		   Было 1.1875rem (19 px), см. разбор порога WCAG выше. */
		font-size: 1.0625rem;
		padding: 0.5625rem 1.25rem;
	}
}

@media (prefers-reduced-motion: reduce) {
	.cases-tab {
		transition: none;
	}
}

/* Лид пустой (поле может быть не задано) — элемент остаётся в DOM,
   в него приезжает описание категории при переключении. */
.cases-head__lead.is-empty {
	display: none;
}

/* Пустая выборка. Пустые термины из ленты отфильтрованы (см. cases-grid.php),
   так что штатно это состояние не всплывает — оно страховка на случай, когда
   выборка схлопнулась уже после рендера: лучше строка, чем дыра в сетке. */
.cases-empty {
	grid-column: 1 / -1;
	margin: 0;
	padding: clamp(2rem, 5vw, 4rem) 0;
	color: var(--macro-muted);
	font-size: 1rem;
	text-align: center;
}

.cases-empty[hidden] {
	display: none;
}


/* --- Подзаголовок категории и второй абзац описания ---
   Появляются при выборе таба, заголовок «Кейсы» и лид блока остаются на месте.
   Свёрнутое состояние — grid-template-rows: 0fr, раскрытое — 1fr: высота
   анимируется, сетка под шапкой не прыгает рывком. Вертикальные отступы живут
   ВНУТРИ (padding-top у span), иначе при свёрнутом состоянии остаётся зазор. */

.cases-head__subtitle,
.cases-head__note {
	display: grid;
	grid-template-rows: 0fr;
	margin: 0;
	opacity: 0;
	transition: grid-template-rows 0.35s cubic-bezier(0.22, 0.61, 0.36, 1),
		opacity 0.25s ease;
}

.cases-head__subtitle > span,
.cases-head__note > span {
	overflow: hidden;
	min-height: 0;
}

.cases-head.has-category .cases-head__subtitle,
.cases-head.has-category-note .cases-head__note {
	grid-template-rows: 1fr;
	opacity: 1;
}

.cases-head__subtitle {
	font-size: clamp(1rem, 1.6vw, 1.375rem);
	font-weight: 500;
	line-height: 1.2;
	color: var(--macro-ink2);
}

.cases-head__subtitle > span {
	padding-top: 0.5rem;
}

.cases-head__note {
	font-size: clamp(1rem, 1.15vw, 1.25rem);
	font-weight: 400;
	line-height: 1.5;
	max-width: 46ch;
	color: var(--macro-muted);
}

.cases-head__note > span {
	padding-top: 0.75rem;
}

@media (prefers-reduced-motion: reduce) {
	.cases-head__subtitle,
	.cases-head__note {
		transition: none;
	}
}

/* На десктопе шапка — 12-колоночный грид, но элементами грида выступают ДВЕ
   колонки-контейнера, а не четыре текстовых блока:
     .cases-head__main  = заголовок + подзаголовок категории (1..7)
     .cases-head__aside = лид блока + описание категории (8..12)
   Так и было задумано, но раньше все четыре лежали прямыми ячейками грида в
   строках 1 и 2 — и высоту строки 1 задавал самый высокий элемент, то есть
   длинный лид справа. Подзаголовок в строке 2 стартовал ниже лида и отваливался
   от h1 на разницу высот (на 1440 это ≈93 px, на длинных лидах больше).
   Контейнеры делают высоты колонок независимыми: внутри каждой обычный поток,
   подзаголовок идёт вплотную под h1 (зазор = padding-top у его span).
   Строки задаём явно, иначе автоплейсмент раскидает логотип и линейку. */
@media (min-width: 992px) {
	.cases-head__logo,
	.cases-head__logo[src$=".svg"] {
		grid-row: 1;
	}

	.cases-head__main {
		grid-column: 1 / span 7;
		grid-row: 2;
		min-width: 0;
	}

	.cases-head__aside {
		grid-column: 8 / span 5;
		grid-row: 2;
		min-width: 0;
	}

	.cases-head__rule {
		grid-column: 1 / -1;
		grid-row: 3;
	}
}


/* --- Панели вкладок главной ---
   ТЗ site/docs/tz/tz-home-tabs.md. Лента .cases-tabs переключает не только
   категории кейсов, но и РАЗДЕЛЫ страницы: панель кейсов живёт внутри этого
   блока (.cases-panel), панели разделов — секции соседних блоков
   (acf/team-members, acf/about-company). Атрибут data-home-panel им проставляет
   cases-grid.js на инициализации; до этого неактивные панели держит служебный
   <style id="home-tabs-boot">, который печатает cases-grid.php. */

/* Спрятанная панель. Специфичность (0,2,1) — перебивает .team-section /
   .about-section без !important; на UA-правило [hidden] полагаться нельзя,
   любое авторское display его снимает. */
[data-home-panel][hidden] {
	display: none;
}

/* Верхний воздух панели держит лента табов (её margin-bottom), собственный
   padding-top панели гасим — иначе под лентой двойной отступ.
   Атрибут проставляет JS, поэтому расхождение живёт один кадр и заметно
   только как чуть больший отступ: контент не прыгает. */
[data-home-panel]:not(.cases-panel) {
	padding-top: 0;
}

/* Содержимое чужих панелей выравниваем по ЛЕВОМУ краю колонки, как сетку
   кейсов. Свои меры у них уже, чем --case-max (у team-section это --case-wide),
   и центрирование внутри полной колонки уводило бы их вправо от ленты табов,
   которая идёт по --case-max. Специфичность (0,2,0) перебивает
   `.team-section > *` (0,1,0). */
[data-home-panel]:not(.cases-panel) > * {
	margin-inline: 0 auto;
}

/* Активна не кейсовая панель — нижний воздух секции держит уже сама панель */
.cases-section.is-panel-hidden {
	padding-bottom: 0;
}

/* Табы стали ссылками (<a href>), а не кнопками: без JS сайт остаётся
   работоспособным, вкладку можно открыть в новом окне. Отсюда две правки
   оформления — базовые правила выше написаны под <button>:
   1) ссылке нужен явный display, иначе padding по вертикали не держит высоту;
   2) глобальное `.dark-theme a:not(.btn):not(…)` имеет специфичность (0,5,1)
      и красит подпись в голубой цвет ссылок. Обычным правилом это не перебить —
      протаскиваем те же контекстные токены через !important, ровно как сделано
      у .top-back в style.css. Своих цветов тут нет. */
.cases-tab {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	text-decoration: none;
}

body.dark-theme .cases-tab,
body.dark-theme .cases-tab:focus,
.single-isotope .cases-tab {
	color: var(--macro-ink2) !important;
}

body.dark-theme .cases-tab.is-active,
.single-isotope .cases-tab.is-active {
	color: var(--macro-accent-ink) !important;
}

@media (hover: hover) and (pointer: fine) {
	body.dark-theme .cases-tab:hover,
	.single-isotope .cases-tab:hover {
		color: var(--macro-ink) !important;
	}

	body.dark-theme .cases-tab.is-active:hover,
	.single-isotope .cases-tab.is-active:hover {
		color: var(--macro-accent-ink) !important;
	}
}
