/* Gotcha — токен-система, тёмная и светлая темы. Без фреймворка, без сборки. */


:root {
	/* color-scheme синхронизирован с темой: без него нативные контролы форм
	   (календарь/спиннеры <input type="datetime-local">, скроллбары) браузер
	   рисует в светлой схеме поверх тёмного полотна. dark по умолчанию,
	   light — в явной светлой теме и при системной светлой (ниже). */
	color-scheme: dark;
	--bg: #0f131b;
	--surface: #171c28;
	--surface-2: #1b2130;
	--border: #263041;
	--text: #e6ebf3;
	--text-dim: #8a93a6;
	/* Измерено: прежний #5c6473 давал 2.70-3.12:1 — «Не назначено» и заголовки
	   дней недели в пикере читались хуже порога 4.5:1 (WCAG 1.4.3). #828b9c
	   даёт 4.68 на surface-2, 4.96 на surface и 5.42 на полотне. */
	--text-mute: #828b9c;
	/* --accent служит ФОНОМ первичной кнопки под белой подписью (--accent-fg),
	   поэтому обязан давать >= 4.5:1 по WCAG 1.4.3. Прежний бренд-синий #3d7bff
	   давал 3.84:1 — главная кнопка действия не проходила на каждом экране.
	   #3169e8 = 4.87:1: минимальное отклонение от бренда с реальным запасом.
	   Бренд-градиент знака (#3d7bff→#7c5cff в icons.templ/favicon.svg) НЕ трогаем:
	   это графика, а не текст на подложке, там действует правило 3:1. */
	--accent: #3169e8;
	--accent-hover: #5a90ff;
	--accent-fg: #ffffff;
	--accent-soft: rgba(96, 132, 255, .12);
	--rail: #0c0f16;
	--rail-border: #1c2534;
	--good: #2ea043;
	--warn: #d9a521;
	/* «Частично / деградация» — самостоятельный чистый жёлтый, отдельный от
	   --warn (мутно-коричневый амбер «предупреждение/нужно улучшить»). Бар
	   доступности, плашка «Частичный сбой» на статус-странице. Светлый вариант
	   темнее для читаемости как текст на белом (--warn-golden, не олива). */
	--partial: #f5b70a;
	/* Заливки полоски доступности (№28): НЕ текстовые статус-токены. Те прижаты
	   к 4.5:1 на подложке и потому неразличимы по светлоте между собой; заливке
	   текстовый контраст не нужен, и светлота разведена: попарно ≥3:1, к
	   карточке ≥1.5:1 (пришпилено TestAvailabilityBarFillTokens). */
	--bar-up: #e1f2e4;
	--bar-partial: #ab7f07;
	--bar-down: #692621;
	--bar-empty: var(--border);
	/* Фазы запроса DNS→TCP→TLS→TTFB: секвенция по бренд-градиенту, тёмный →
	   светлый = порядок во времени. Значения разведены под
	   TestLatencyPhaseTokens: ≥3:1 к карточке, соседи ≥1.3:1, монотонность. */
	--phase-dns: #2b61e8;
	--phase-tcp: #6770f7;
	--phase-tls: #9d81ff;
	--phase-ttfb: #b1a7d9;
	--danger: #f0574a;
	--info: #3d7bff;
	--link: #79aaff;
	/* --accent-ink — текст поверх БЛЕДНЫХ подложек (--accent-soft, заливка
	   баннера квоты). На них --link светлой темы давал 4.05:1 у инициалов и
	   4.45:1 у ссылки баннера — ниже порога 4.5:1. В тёмной теме запас есть,
	   поэтому значение совпадает с --link. */
	--accent-ink: #79aaff;
	/* --*-ink — цвет ТЕКСТА на собственном 16%-тинте (бейджи, плашки статуса).
	   Допущение «в тёмной теме тинт ложится на тёмную подложку, поэтому ink
	   можно оставить равным токену» оказалось НЕВЕРНЫМ: тинт подложку
	   осветляет, и по пикселям реального рендера получалось 3.66-4.15:1.
	   Жёлтые (--warn, --partial) и так с запасом, остальные три осветлены
	   расчётом до >= 4.5 на собственном тинте. В светлой теме тинт осветляет фон,
	   а текст оставался прежним — измерено 3.10-3.69:1 вместо 4.5, а плашка
	   «Частичный сбой» на ПУБЛИЧНОЙ статус-странице давала 2.47:1, худший
	   показатель во всём продукте. */
	/* --border-control — граница ИНТЕРАКТИВНЫХ элементов (поля, селекты,
	   ghost-кнопки, чекбоксы). Отдельно от --border намеренно: WCAG 1.4.11
	   требует 3:1 для границ контролов, но не для разделителей и рамок карточек,
	   а поднимать контраст у всех рамок сразу — это визуально совсем другой,
	   заметно более тяжёлый интерфейс. Измерено на прежнем --border: 1.40:1 в
	   тёмной теме и 1.21:1 в светлой, тогда как граница поля — единственное, что
	   показывает, куда кликать. */
	--border-control: #657699;
	--good-ink: #49ac5b;
	--warn-ink: var(--warn);
	--partial-ink: var(--partial);
	--danger-ink: #f26d62;
	--info-ink: #6294ff;
	--radius: 6px;
	--radius-lg: 10px;
	--space: 4px;
	--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	--font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
	/* Шкала кеглей одним местом: раньше размеры были захардкожены в двух
	   десятках правил, и подгонка «чуть крупнее» означала правку каждого.
	   --fs-base — обычный текст, --fs-sm — вторичный (ячейки таблиц, мета),
	   --fs-xs — мелкие подписи и бейджи, --fs-label — микро-лейблы капсом
	   (заголовки колонок, подписи стат-плиток, ярлыки рейла). */
	--fs-base: 15px;
	--fs-sm: 14px;
	--fs-xs: 12px;
	--fs-label: 11px;
}
:root[data-theme="light"] {
	color-scheme: light;
	--bg: #f6f8fb;
	--surface: #ffffff;
	--surface-2: #eef2f7;
	--border: #dde3ec;
	--text: #1a2130;
	--text-dim: #5a6472;
	/* 3.87:1 на рейле — ниже порога; #5f6878 даёт 4.61 там и 5.0+ на остальных. */
	--text-mute: #5f6878;
	--accent: #2f6bff;
	--accent-hover: #1f5aff;
	--accent-fg: #ffffff;
	--accent-soft: rgba(47, 107, 255, .10);
	/* Рейл заметно глубже полотна и сайдбара: на светлой теме он иначе
	   сливается с сайдбаром в одну серую полосу и перестаёт читаться как
	   отдельная зона навигации. Тёмным (как в спеке §4.1) не делаем —
	   сплошная тёмная колонка на светлом фоне читается как артефакт. */
	--rail: #e4e9f2;
	--rail-border: #ccd5e2;
	/* Контраст светлой темы измерен на живом DOM (WCAG 1.4.3, нужно 4.5:1).
	   Прежние значения не добирали почти везде: --link 4.00 на surface-2,
	   --partial 3.38, --text-mute 2.75. Тёмная тема при этом шла с запасом
	   (--link 6.9-8.2), то есть перекос был ровно в одну сторону. Значения ниже
	   проходят 4.5:1 на всех четырёх подложках (surface/surface-2/bg/rail). */
	--good: #187633;
	--warn: #8c5e00;
	--partial: #8e5d00;
	--bar-up: #a4d5ad;
	--bar-partial: #926209;
	--bar-down: #34090c;
	--bar-empty: var(--border);
	--phase-dns: #193887;
	--phase-tcp: #4b52b4;
	--phase-tls: #7e65d0;
	--phase-ttfb: #9282c9;
	--danger: #c3202b;
	--info: #295ee0;
	--link: #295ee0;
	--accent-ink: #1a4fc4;
	/* Текст на собственном 16%-тинте — темнее самого токена: тинт
	   осветляет фон, а текст прежде не осветлялся вместе с ним.
	   Значения проверены расчётом: >= 4.5:1 и на белой карточке,
	   и на surface-2. */
	--border-control: #7e8b9f;
	--good-ink: #166b2e;
	--warn-ink: #7d5400;
	--partial-ink: #7f5300;
	--danger-ink: #b01d26;
	--info-ink: #2454c9;
}
@media (prefers-color-scheme: light) {
	:root:not([data-theme="dark"]) {
		color-scheme: light;
		--bg: #f6f8fb;
		--surface: #ffffff;
		--surface-2: #eef2f7;
		--border: #dde3ec;
		--text: #1a2130;
		--text-dim: #5a6472;
		/* 3.87:1 на рейле — ниже порога; #5f6878 даёт 4.61 там и 5.0+ на остальных. */
	--text-mute: #5f6878;
		--accent: #2f6bff;
		--accent-hover: #1f5aff;
		--accent-fg: #ffffff;
		--accent-soft: rgba(47, 107, 255, .10);
		--rail: #e4e9f2;
		--rail-border: #ccd5e2;
		/* Контраст светлой темы измерен на живом DOM (WCAG 1.4.3, нужно 4.5:1).
		   Прежние значения не добирали почти везде: --link 4.00 на surface-2,
		   --partial 3.38, --text-mute 2.75. Тёмная тема при этом шла с запасом
		   (--link 6.9-8.2), то есть перекос был ровно в одну сторону. Значения ниже
		   проходят 4.5:1 на всех четырёх подложках (surface/surface-2/bg/rail). */
		--good: #187633;
		--warn: #8c5e00;
		--partial: #8e5d00;
		--bar-up: #a4d5ad;
		--bar-partial: #926209;
		--bar-down: #34090c;
		--bar-empty: var(--border);
		--phase-dns: #193887;
		--phase-tcp: #4b52b4;
		--phase-tls: #7e65d0;
		--phase-ttfb: #9282c9;
		--danger: #c3202b;
		--info: #295ee0;
		--link: #295ee0;
	--accent-ink: #1a4fc4;
		/* Текст на собственном 16%-тинте — темнее самого токена: тинт
		   осветляет фон, а текст прежде не осветлялся вместе с ним.
		   Значения проверены расчётом: >= 4.5:1 и на белой карточке,
		   и на surface-2. */
		--border-control: #7e8b9f;
		--good-ink: #166b2e;
		--warn-ink: #7d5400;
		--partial-ink: #7f5300;
		--danger-ink: #b01d26;
		--info-ink: #2454c9;
	}
}

* {
	box-sizing: border-box;
}

body {
	margin: 0;
	background: var(--bg);
	color: var(--text);
	font-family: var(--font-sans);
	font-size: var(--fs-base);
	line-height: 1.5;
}

/* Типографическая шкала (спека §4.2, компактная плотность). Без этих правил
   заголовки берут дефолт браузера (h1 = 2em), и шапка каждой страницы
   получается втрое крупнее строки таблицы — из компактной вёрстки это
   выбивается сильнее всего. */
h1 {
	font-size: 22px;
	font-weight: 650;
	line-height: 1.25;
	margin: 0 0 10px;
}
h2 {
	font-size: 18px;
	font-weight: 650;
	line-height: 1.3;
	margin: 18px 0 8px;
}
h3 {
	font-size: 15px;
	font-weight: 600;
	line-height: 1.3;
	margin: 14px 0 6px;
}

code, pre {
	font-family: var(--font-mono);
}
/* Инлайновый <code> (redirect URI в настройках SSO, токены) на узком экране
   рвётся в любом месте: у него, в отличие от <pre>, нет своего скролла, и
   длинное значение распирало страницу (маскировалось скроллером
   .app-content, №82). */
code {
	overflow-wrap: anywhere;
}
pre {
	overflow-x: auto;
	max-width: 100%;
}

a {
	color: var(--link);
	text-decoration: none;
}
a:hover {
	text-decoration: underline;
}

/* ============================================================
   App shell — icon rail + context sidebar + topbar (Task 4, §5 IA).
   Компактно, всё через токены (кроме фирменного градиента лого-
   типа рейла — фиксированная марка бренда, см. .rail-logo).
   ============================================================ */
.app-shell {
	display: grid;
	grid-template-columns: 96px 200px 1fr;
	min-height: 100vh;
}

/* -- Icon rail: логотип, области нав-структуры, аватар, логаут -- */
.rail {
	background: var(--rail);
	border-right: 1px solid var(--rail-border);
	display: flex;
	flex-direction: column;
	align-items: center;
	padding: 8px 0;
	gap: 2px;
}
.rail-logo {
	width: 30px;
	height: 30px;
	display: flex;
	align-items: center;
	justify-content: center;
	margin-bottom: 8px;
	text-decoration: none;
}
/* Фирменный знак: моно-«G» в уголковой рамке-прицеле (см. brandMark в
   icons.templ). Размер задаётся здесь, не атрибутами SVG. */
.brand-mark {
	display: block;
	width: 30px;
	height: 30px;
}
.rail-area {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 3px;
	width: 100%;
	padding: 7px 4px;
	color: var(--text-dim);
	text-decoration: none;
}
.rail-area:hover {
	color: var(--text);
}
/* Двутон: иконка на мягкой плитке, активная область — плитка акцентом */
.rail-area .rail-ic {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 30px;
	border-radius: 8px;
	background: var(--accent-soft);
}
.rail-area .ic {
	width: 18px;
	height: 18px;
}
.rail-area.is-active {
	color: var(--text);
}
.rail-area.is-active .rail-ic {
	background: var(--accent);
}
.rail-area.is-active .ic {
	color: var(--accent-fg);
}
.rail-label {
	font-size: var(--fs-label);
	line-height: 1.1;
	letter-spacing: .2px;
	text-align: center;
	/* Мягкие переносы (U+00AD) из каталога локалей убраны: они ломали поиск по
	   странице — Ctrl+F по «Производительность» не находил ярлык, потому что
	   внутри слова стоял невидимый символ. Ярлык теперь переносится целыми
	   словами, а если не влезает — усекается многоточием: обрезанная подпись
	   читается, а ненаходимая — нет.

	   Полное название доступно всегда: оно же стоит в title ссылки. */
	hyphens: manual;
	overflow-wrap: normal;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.rail-spacer {
	flex: 1;
}
.rail-logout,
.rail-logout .rail-logout-btn {
	background: none;
	border: none;
	color: var(--text-dim);
	cursor: pointer;
	padding: 6px;
	margin: 0;
	display: flex;
	align-items: center;
	justify-content: center;
}
.rail-logout-btn:hover {
	background: none;
	color: var(--danger);
}
.rail-logout-btn .ic {
	width: 18px;
	height: 18px;
}
.avatar {
	width: 28px;
	height: 28px;
	border-radius: 50%;
	background: var(--accent-soft);
	/* --accent-ink, а не --link и тем более не --accent: первый рассчитан как
	   ФОН под белой подписью (3.28-3.44:1 на инициалах), второй — как текст на
	   обычных подложках и на бледной --accent-soft давал 4.05:1. */
	color: var(--accent-ink);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: var(--fs-xs);
	font-weight: 600;
	text-decoration: none;
	margin-top: 6px;
}

/* -- Context sidebar: переключатель проекта + подразделы области -- */
.ctx-sidebar {
	background: var(--surface);
	border-right: 1px solid var(--border);
	display: flex;
	flex-direction: column;
	padding: 8px;
	gap: 2px;
	overflow-y: auto;
}
.proj-switch {
	margin-bottom: 8px;
}
.proj-switch summary {
	list-style: none;
	cursor: pointer;
	padding: 7px 9px;
	background: var(--surface-2);
	border: 1px solid var(--border-control);
	border-radius: var(--radius);
	font-weight: 600;
	color: var(--text);
	overflow-wrap: anywhere;
}
.proj-switch summary::-webkit-details-marker {
	display: none;
}
.proj-switch summary {
	display: flex;
	align-items: center;
	gap: 6px;
}
.proj-switch-name {
	flex: 1;
	min-width: 0;
	overflow-wrap: anywhere;
}
.proj-switch summary .ic {
	flex: none;
	color: var(--text-dim);
	transition: transform 120ms ease;
}
.proj-switch[open] summary .ic {
	transform: rotate(180deg);
}
.proj-switch ul {
	list-style: none;
	margin: 4px 0 0;
	padding: 4px;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
}
.proj-switch li a {
	display: block;
	padding: 5px 8px;
	border-radius: var(--radius);
	color: var(--text-dim);
	text-decoration: none;
}
.proj-switch li a:hover {
	background: var(--surface-2);
	color: var(--text);
}
.ctx-item {
	display: block;
	padding: 6px 10px;
	border-radius: var(--radius);
	color: var(--text-dim);
	text-decoration: none;
	font-size: var(--fs-sm);
	overflow-wrap: anywhere;
}
.ctx-item:hover {
	background: var(--surface-2);
	color: var(--text);
}
.ctx-item.is-active {
	background: var(--surface-2);
	color: var(--text);
	box-shadow: inset 2px 0 0 var(--accent);
}
.ctx-settings {
	margin-top: auto;
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 6px 10px;
	color: var(--text-dim);
	text-decoration: none;
	border-top: 1px solid var(--border);
}
.ctx-settings .ic {
	width: 15px;
	height: 15px;
}
.ctx-settings:hover {
	color: var(--text);
}

/* -- Main column: topbar (breadcrumb + переключатели) + контент -- */
.app-main {
	display: flex;
	flex-direction: column;
	min-width: 0;
}
.topbar {
	height: 44px;
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 16px;
	border-bottom: 1px solid var(--border);
	background: var(--surface);
}
.topbar-spacer {
	flex: 1;
}
/* Сегмент-контролы: кнопки склеены в одну пилюлю (общие границы, без
   зазора), поэтому читаются как один переключатель с выбранным значением, а
   не как три самостоятельные кнопки. */
.lang-switcher,
.theme-toggle {
	display: inline-flex;
	align-items: center;
	vertical-align: middle;
	border: 1px solid var(--border-control);
	border-radius: var(--radius);
	overflow: hidden;
	/* flex: none обязателен вместе с overflow: hidden. Без него переключатели
	   сжимались наравне с остальным содержимым topbar и ОБРЕЗАЛИСЬ: на 375px
	   кнопка «EN» пропадала целиком, а «Системная» уезжала за вьюпорт — по её
	   центру elementFromPoint возвращал шапку, то есть нажать было физически
	   нельзя. Сжиматься должна хлебная крошка (см. .breadcrumb ниже). */
	flex: none;
}
/* Компактные сегментированные переключатели — переопределяем громкую
   акцентную кнопку по умолчанию (см. правило `button` ниже), чтобы в 44px
   topbar они не спорили с основными действиями. */
.lang-switcher button,
.theme-toggle button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: transparent;
	border: none;
	/* --border-control, а не --border: разделитель здесь ЕДИНСТВЕННОЕ, что
	   показывает границы кнопки, то есть он несёт смысл, а не декорацию
	   (WCAG 1.4.11). На --border измерено 1.40:1 в тёмной теме. */
	border-left: 1px solid var(--border-control);
	color: var(--text-dim);
	/* Цель нажатия не меньше 24×24 (WCAG 2.5.8): было 33×22 и 30×22, причём
	   кнопки стоят вплотную, поэтому исключение по расстоянию не применяется. */
	min-width: 24px;
	min-height: 24px;
	padding: 4px 8px;
	font-size: var(--fs-xs);
	font-weight: 600;
	cursor: pointer;
	border-radius: 0;
}
.lang-switcher button:first-child,
.theme-toggle button:first-child {
	border-left: none;
}
.theme-toggle .ic {
	width: 14px;
	height: 14px;
}
.lang-switcher button:hover:not(.is-active),
.theme-toggle button:hover:not(.is-active) {
	color: var(--text);
	border-color: var(--text-dim);
}
/* Активное состояние переключателей языка и темы.
   --accent здесь НЕ подходит как цвет текста: токен рассчитан как ФОН под белой
   подписью (см. его комментарий выше), и на бледной подложке --accent-soft он
   давал 2.98:1 в тёмной теме и 3.95:1 в светлой — то есть «какой язык выбран»
   практически не читалось. --link рассчитан именно как текст и даёт 6.2:1.
   Заодно подложке добавлена граница: сама заливка --accent-soft отличается от
   окружения на 1.17:1, одной ею состояние не показать. */
.lang-btn.is-active,
.theme-btn.is-active {
	background: var(--accent-soft);
	color: var(--link);
	border-color: color-mix(in srgb, var(--link) 45%, transparent);
}
/* Skip-link: вне потока и невидим, пока не получит фокус с клавиатуры. Не
   display:none и не visibility:hidden — иначе он выпадет и из порядка
   табуляции, ради которого существует. */
.skip-link {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 100;
	padding: 10px 16px;
	background: var(--accent);
	color: var(--accent-fg);
	border-radius: 0 0 var(--radius) 0;
	font-weight: 600;
}
.skip-link:focus {
	left: 0;
}
/* Фокус на области содержимого виден. Раньше здесь стоял outline:none, и
   переход по skip-link уводил фокус в никуда: клавиатурный пользователь
   нажимал «Перейти к содержимому» и терял место на странице (WCAG 2.4.7).
   :focus-visible, а не :focus: программный фокус после перехода по якорю
   браузер считает видимым, а щелчок мышью — нет. */
.app-content:focus { outline: none; }
.app-content:focus-visible {
	outline: 2px solid var(--link);
	outline-offset: -2px;
}

.app-content {
	flex: 1;
	padding: 16px 24px;
	min-width: 0;
	/* overflow-x здесь больше нет: единственной причины (широкие таблицы
	   распирали колонку) не стало — вся широкая вёрстка сидит в собственных
	   скролл-регионах (.table-scroll и семья). Скроллер на контентной колонке
	   превращал overflow-y в auto и обрезал попап пикера дат на коротких
	   страницах (№82). */
	/* Контент прижат влево, а не центрирован: оболочка выстроена слева
	   направо (рейл → сайдбар → контент), и центрирование разрывало эту
	   цепочку — левый край контента переставал совпадать с краем сайдбара,
	   и слева появлялось сиротское поле.
	   Потолок ширины сохраняем: без него таблица из восьми колонок на
	   ультравайде растягивается на 2400px, и связать первую колонку с
	   последней глазом уже трудно. На 1920-мониторе потолок не срабатывает. */
	max-width: 1920px;
	width: 100%;
	margin: 0;
}

.app-footer {
	padding: 8px 16px;
	font-size: 12px;
	text-align: right;
	color: var(--text-dim);
}
.app-footer a {
	color: var(--link);
	text-decoration: none;
}
.app-footer a:hover {
	text-decoration: underline;
}

/* Скролл-контейнер для длинных списков (участники и т.п.): ограничивает
   высоту блока и включает вертикальный скролл, чтобы 100 строк не растягивали
   страницу. */
/* Формы внутри ячеек таблицы выкладываются в строку. Без этого .select с его
   width:100% распирает ячейку и выталкивает кнопку на вторую строку, хотя
   места в карточке вдоволь. */
.member-role-form {
	display: flex;
	align-items: center;
	gap: 6px;
}
/* Формы добавления в команде: у select есть подпись сверху, поэтому кнопку
   выравниваем по НИЖНЕМУ краю поля (= низ select), иначе она центрируется
   относительно «подпись + select» и висит выше самого поля. */
.team-member-add-form,
.team-project-attach-form {
	display: flex;
	align-items: end;
	gap: 6px;
	/* flex-wrap обязателен: без него на 375px форма шире контейнера, и кнопка
	   («Добавить участника», «Привязать проект») уезжает за край во вложенный
	   горизонтальный скроллер. Вложенная горизонтальная прокрутка внутри
	   вертикально скроллящейся страницы на тач-устройстве — из худших
	   взаимодействий: пользователь просто не находит кнопку. */
	flex-wrap: wrap;
}
/* .field тянет нижний margin — в этой inline-форме он не нужен, иначе кнопка
   встаёт ниже select на величину этого отступа. */
.team-member-add-form .field,
.team-project-attach-form .field {
	margin-bottom: 0;
}
.member-role-form .select,
.team-member-add-form .select,
.team-project-attach-form .select {
	width: auto;
	min-width: 140px;
}

/* Прокручиваемый список: нижний край растворяется, чтобы обрезанная строка
   читалась как «список продолжается», а не как сломанная вёрстка. */
/* Скролл-контейнер широкой таблицы. Без него горизонтально уезжала ВСЯ
   контентная колонка (единственным скроллером был .app-content), утаскивая с
   собой заголовок страницы и фильтры: на 375px таблица эндпойнтов шириной 745px
   в колонке 216px делала страницу непригодной. Таблица скроллится сама. */
.table-scroll {
	overflow-x: auto;
	max-width: 100%;
}
/* Скролл-регионы фокусируемы (tabindex=0 из scrollRegion): без видимого
   кольца клавиатурный фокус на контейнере неотличим от его отсутствия
   (WCAG 2.4.7). :focus-visible, а не :focus — щелчок мышью кольца не рисует. */
.table-scroll:focus, .scroll-list:focus, .endpoint-chart:focus,
.metric-chart-wrap:focus, .flamegraph-wrap:focus, .trace-waterfall:focus,
.trace-flame:focus, .issue-chart:focus { outline: none; }
.table-scroll:focus-visible, .scroll-list:focus-visible,
.endpoint-chart:focus-visible, .metric-chart-wrap:focus-visible,
.flamegraph-wrap:focus-visible, .trace-waterfall:focus-visible,
.trace-flame:focus-visible, .issue-chart:focus-visible {
	outline: 2px solid var(--link);
	outline-offset: -2px;
}
.table-scroll > .data-table {
	width: 100%;
}

.scroll-list {
	max-height: 340px;
	overflow-y: auto;
	/* И по горизонтали тоже: этот контейнер держит .data-table (orgsettings),
	   которая на узком экране шире него. */
	overflow-x: auto;
	/* Маски-градиента здесь больше нет: она выцвечивала нижние 24px НЕЗАВИСИМО
	   от того, есть ли что прокручивать, поэтому последняя строка короткого
	   списка участников выглядела блёклой без всякой причины. CSS не умеет
	   отличить «список прокручивается» от «список короткий», а нечитаемая
	   строка дороже, чем подсказка о прокрутке. */
}

/* No-JS модальное окно через :target (CSP запрещает инлайн-JS). Триггер —
   ссылка на #id; оверлей скрыт, показывается при совпадении :target; закрытие
   — ссылка-фон и крестик ведут на "#" (пустой фрагмент снимает :target). */
.modal {
	display: none;
	position: fixed;
	inset: 0;
	z-index: 100;
	background: rgba(0, 0, 0, .55);
	align-items: flex-start;
	justify-content: center;
	padding: 56px 16px;
	overflow-y: auto;
}
.modal:target,
/* .modal--open — серверное открытие. При ошибке валидации страница
   отрисовывается заново, фрагмента #id в адресе уже нет, и модалка закрывалась
   вместе с введёнными данными: человек заполнял семь полей, ошибался в одном и
   получал пустую форму и сообщение где-то вверху страницы. */
.modal--open {
	display: flex;
}
/* Пустой якорь перед модалкой; сам по себе невидим. */
.modal-dismiss {
	display: block;
	height: 0;
}
/* Закрытие серверно открытой модалки. Она держится классом, а не :target,
   поэтому переход на "#" её не закрывал — крестик и клик по фону не работали
   вовсе. Переход на якорь закрытия включает это правило, а его специфичность
   (три класса) перебивает .modal--open (один). */
.modal-dismiss:target + .modal--open {
	display: none;
}
.modal-backdrop {
	position: absolute;
	inset: 0;
}
.modal-card {
	position: relative;
	z-index: 1;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	padding: 20px 24px;
	width: 100%;
	max-width: 460px;
	box-shadow: 0 12px 40px rgba(0, 0, 0, .35);
}
/* Широкая модалка — для форм, где узкая колонка рвёт вёрстку (длинные подписи
   полей). */
.modal-card--wide {
	max-width: 560px;
}
.modal-close {
	position: absolute;
	top: 12px;
	right: 14px;
	color: var(--text-dim);
	font-size: 22px;
	line-height: 1;
	text-decoration: none;
}
.modal-close:hover {
	color: var(--text);
}
/* Кнопка-триггер создания сущности (в шапке раздела). */
.create-trigger {
	margin-bottom: 16px;
}
/* Две подформы «Данные субъекта (ПДн)» рядом (экспорт | удаление). */
.gdpr-grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: 16px 24px;
}
@media (min-width: 900px) {
	.gdpr-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Карточки правил алертинга (new_issue/regression/spike) рядами — было
   вертикальным стеком трёх fieldset'ов, слишком высоко/разреженно. */
/* Правила алертов: на узком экране — колонкой. */
.rule-grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: 16px;
	margin-bottom: 16px;
}
/* На широком экране — две колонки: слева «Новый issue» и под ним «Регрессия»
   (равны между собой — одинаковое содержимое, по одному полю), справа
   «Всплеск» на всю высоту левой пары (grid-row 1/span 2, растянут). Порядок
   в DOM: new_issue(1), regression(2), spike(3). */
@media (min-width: 900px) {
	.rule-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		grid-auto-rows: 1fr;
		align-items: stretch;
	}
	.rule-grid > :nth-child(1) { grid-column: 1; grid-row: 1; }
	.rule-grid > :nth-child(2) { grid-column: 1; grid-row: 2; }
	.rule-grid > :nth-child(3) { grid-column: 2; grid-row: 1 / span 2; }
	/* Карточки-fieldset тянутся на всю высоту своей ячейки, а не по контенту:
	   иначе короткие «Новый issue»/«Регрессия» слева не дотягивались до низа
	   высокого «Всплеска» справа — сетка читалась «ступенькой». */
	.rule-grid > .alert-rule { height: 100%; margin: 0; }
}

/* -- Chromeless: анонимные/setup-страницы (логин, регистрация, онбординг,
   ошибки) — без рейла и сайдбара, тонкая верхняя полоса + центр-форма -- */
.chromeless-top {
	height: 48px;
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 16px;
	border-bottom: 1px solid var(--border);
	background: var(--surface);
}
.chromeless-main {
	max-width: 520px;
	margin: 40px auto;
	padding: 0 16px;
}
/* Бренд-лок-ап (знак + вордмарк) в тонкой верхней полосе chromeless-страниц. */
.brand-lockup {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	text-decoration: none;
}
.brand-lockup .brand-mark {
	width: 26px;
	height: 26px;
}
.brand-word {
	font-weight: 700;
	font-size: 16px;
	letter-spacing: -.01em;
	color: var(--text);
}

/* Бренд-момент на логине: крупный знак + вордмарк + слоган голосом продукта. */
.auth-brand {
	max-width: 360px;
	margin: 40px auto 0;
	text-align: center;
}
.auth-brand .brand-mark {
	width: 52px;
	height: 52px;
	margin: 0 auto 8px;
}
.auth-brand-word {
	display: block;
	font-weight: 700;
	font-size: 26px;
	letter-spacing: -.02em;
	color: var(--text);
}
.auth-tagline {
	margin: 6px 0 0;
	color: var(--text-dim);
	font-size: var(--fs-base);
}
.auth-brand + .auth-form {
	margin-top: 16px;
}

/* -- Мобильное disclosure-меню в topbar: дублирует содержимое .ctx-sidebar
   (переключатель проекта, подразделы, настройки проекта) без JS — работает
   через нативные <details>/<summary>. На десктопе скрыто, т.к. тот же
   контент уже доступен в .ctx-sidebar (см. адаптив ниже). -- */
.mobile-nav {
	display: none;
}
.mobile-nav-summary {
	list-style: none;
	cursor: pointer;
	padding: 6px;
	display: inline-flex;
	align-items: center;
	border-radius: var(--radius);
	color: var(--text-dim);
}
.mobile-nav-summary::-webkit-details-marker {
	display: none;
}
.mobile-nav-summary:hover {
	background: var(--surface-2);
	color: var(--text);
}
.mobile-nav-panel {
	position: absolute;
	top: 100%;
	left: 0;
	z-index: 20;
	min-width: 220px;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 8px;
	display: flex;
	flex-direction: column;
	gap: 2px;
	box-shadow: 0 8px 24px rgba(0, 0, 0, .3);
}

/* -- Адаптив: на узких экранах сайдбар подразделов скрывается, рейл
   остаётся всегда доступным; вместо сайдбара — мобильное disclosure-меню
   в topbar (см. .mobile-nav выше) -- */
@media (max-width: 900px) {
	.app-shell {
		grid-template-columns: 96px 1fr;
	}
	.ctx-sidebar {
		display: none;
	}
	.mobile-nav {
		display: block;
		position: relative;
	}
}

/* Ниже 560px рейл ужимается до иконок: 96px колонка съедала четверть вьюпорта
   телефона, из-за чего topbar оставался тесным даже после flex: none выше. */
@media (max-width: 560px) {
	.app-shell {
		grid-template-columns: 56px 1fr;
	}
	.rail-label {
		display: none;
	}
	.topbar {
		padding: 0 8px;
		gap: 4px;
	}
}

main {
	max-width: 960px;
	margin: 0 auto;
	padding: 24px;
}

.auth-form {
	max-width: 360px;
	margin: 40px auto;
	display: flex;
	flex-direction: column;
	gap: 12px;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 24px;
}
.auth-form h1 {
	margin: 0 0 4px;
	font-size: 20px;
}
.auth-switch {
	margin: 0;
	font-size: var(--fs-base);
	color: var(--text-dim);
}

label {
	display: flex;
	flex-direction: column;
	gap: 4px;
	font-size: var(--fs-base);
	color: var(--text-dim);
}

input {
	background: var(--bg);
	border: 1px solid var(--border-control);
	border-radius: var(--radius);
	color: var(--text);
	padding: 6px 10px;
	font-size: var(--fs-base);
}
input:focus {
	outline: 2px solid var(--accent);
	outline-offset: -1px;
}

button {
	background: var(--accent);
	color: var(--accent-fg);
	border: none;
	border-radius: var(--radius);
	padding: 6px 12px;
	font-size: var(--fs-base);
	font-weight: 600;
	cursor: pointer;
}
button:hover {
	background: var(--accent-hover);
}

/* Текст берёт --danger-ink, а не сырой --danger: последний рассчитан как ФОН
   бейджа, и на бледной подложке плашки давал 4.15:1 — ниже 4.5:1 (WCAG 1.4.3).
   Тот же --danger-ink в бейджах даёт 5.8:1. */
.error {
	background: color-mix(in srgb, var(--danger) 16%, transparent);
	border: 1px solid var(--danger);
	color: var(--danger-ink);
	border-radius: var(--radius);
	padding: 6px 10px;
	font-size: var(--fs-base);
	margin: 0;
}

.quota-banner {
	display: flex;
	/* Перенос обязателен: на 375px текст + ссылка «Настройки приёма» в одну
	   строку распирали страницу (раньше это маскировал скроллер .app-content,
	   №82). */
	flex-wrap: wrap;
	align-items: center;
	gap: 10px;
	background: color-mix(in srgb, var(--warn) 16%, transparent);
	border: 1px solid var(--warn);
	border-radius: var(--radius);
	padding: 8px 12px;
	font-size: var(--fs-base);
	margin: 0 0 12px;
}
/* Разбивка по видам приёма: общее число не говорит, какую квоту поднимать. */
.quota-banner-detail {
	color: var(--text-dim);
}
.quota-banner a {
	white-space: nowrap;
	/* Заливка баннера бледно-жёлтая: обычный --link давал на ней 4.45:1. */
	color: var(--accent-ink);
}

/* То же, что у .error: сырой --info на своей подложке давал 3.65:1. */
.notice {
	background: color-mix(in srgb, var(--info) 16%, transparent);
	border: 1px solid var(--info);
	color: var(--info-ink);
	border-radius: var(--radius);
	padding: 6px 10px;
	font-size: var(--fs-base);
	margin: 0;
}

/* Итог удаления ПДн, когда не нашлось ни одной строки: это не ошибка, но и не
   успех — оператор обязан отличать «удалено N» от «не найдено ничего». */
.notice--warn {
	background: color-mix(in srgb, var(--warn) 14%, transparent);
	border-color: var(--warn);
	color: var(--warn);
}

/* Подсказка о том, что критерий поиска на этом инстансе заведомо пуст. */
.hint--warn {
	color: var(--warn);
}

.warning {
	background: color-mix(in srgb, var(--warn) 14%, transparent);
	color: var(--warn);
	border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
	border-radius: var(--radius);
	padding: 8px 12px;
}

/* Приглушённые пояснительные абзацы (подсказки полей, заметки о лимитах). */
.hint, .apdex-note, .perf-cap-note, .trace-cap-note, .retention-notice {
	color: var(--text-dim);
	font-size: var(--fs-sm);
	margin: 4px 0;
}

table {
	width: 100%;
	border-collapse: collapse;
	font-variant-numeric: tabular-nums;
}
th, td {
	text-align: left;
	padding: 6px 10px;
	border-bottom: 1px solid var(--border);
	font-size: var(--fs-base);
}
th {
	color: var(--text-dim);
	font-weight: 600;
	font-size: var(--fs-sm);
	text-transform: uppercase;
}

.badge {
	display: inline-block;
	border-radius: 999px;
	padding: 2px 8px;
	font-size: var(--fs-xs);
	font-weight: 700;
	text-transform: uppercase;
	/* Единый трекинг для всех капс-пилюль. Базовый .badge был без него, а
	   соседние капс-элементы (ctx-title, статус-плашки) — с .04em/.4px, отчего
	   бейджи читались чуть плотнее прочего капса. Все семейства (уровень/статус/
	   канал/вайтлс/монитор/инцидент) идут через .badge + .badge-{good,warn,
	   danger,neutral,info}, так что одно правило выравнивает трекинг везде. */
	letter-spacing: .4px;
	white-space: nowrap;
}
/* Inline checkbox + подпись (используется формами алертов/мониторов/обслуживания
   и др.): чекбокс и текст на одной строке, кликабельная подпись целиком. */
.checkbox-inline {
	display: inline-flex;
	flex-direction: row;
	align-items: center;
	gap: 8px;
	color: var(--text);
	font-size: var(--fs-base);
	cursor: pointer;
}
/* Внутри подписи расстояние держит gap контейнера, поэтому собственное поле
   контрола снимаем. Размер НЕ трогаем: раньше здесь стояло width: auto, и оно
   возвращало браузерные 13×13 — то есть отменяло размер цели нажатия ровно
   там, где чекбоксы и живут. Цель нажатия здесь и без того вся подпись:
   <label> оборачивает и контрол, и текст. */
.checkbox-inline input[type="checkbox"],
.checkbox-inline input[type="radio"] {
	margin: 0;
	padding: 0;
}
/* Флеймграф-SVG (viewBox без width/height) — растянуть на ширину карточки. */
.flamegraph {
	width: 100%;
	height: auto;
	display: block;
}
/* SVG-графики (viewBox без width/height) — растянуть на ширину карточки. */
.metric-chart, .waterfall {
	width: 100%;
	height: auto;
	display: block;
}
.waterfall-label {
	font-family: var(--font-mono);
	font-size: var(--fs-label);
	/* Без явного fill SVG-текст рисуется чёрным (дефолт SVG) — на светлой теме
	   читаемо, а на тёмной чёрное на тёмном пропадает. Привязываем к теме. */
	fill: var(--text);
}
.badge-info { background: color-mix(in srgb, var(--info) 16%, transparent); color: var(--info-ink); }

.vital-value { margin-right: 6px; }
/* Аббревиатуры web-vital'ов (LCP/INP/CLS/FCP/TTFB) с расшифровкой по наведению
   (нативный <abbr title>): пунктирное подчёркивание и курсор-help подсказывают,
   что на них можно навести. Наследует цвет ссылки в заголовках-сортировках. */
.vital-abbr, .metric-abbr {
	text-decoration: underline dotted;
	text-underline-offset: 2px;
	cursor: help;
}
/* Подпись поля-формы с всплывающей расшифровкой (title). Тот же язык, что у
   .vital-abbr: пунктир + курсор-help. Для «непрозрачных» технических полей
   настроек производительности/регрессий (см. helpLabel в projsettings.templ). */
.help-label {
	text-decoration: underline dotted;
	text-underline-offset: 3px;
	cursor: help;
}
.vital-chart {
	display: inline-block;
	width: 240px;
	height: 48px;
	vertical-align: middle;
	color: var(--accent);
}
.vital-chart-cell {
	white-space: nowrap;
}

.sparkline {
	display: inline-block;
	width: 96px;
	height: 24px;
	vertical-align: middle;
	color: var(--accent);
}

/* -- Фильтр-панели разделов: единый компактный горизонтальный тулбар.
   Общий для Issues / Performance / Web Vitals / Metrics. Тулбар намеренно
   визуально тихий — герой страницы это таблица/график под ним. Контролы
   по содержимому (а не во всю ширину), поиск слегка растягивается, кнопка
   «Применить» замыкает ряд справа. На узких экранах ряд переносится. -- */
.issues-filters,
.performance-filters,
.metric-filters {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: 8px;
	margin-bottom: 16px;
}
/* Контролы тулбара — по содержимому. Базовое .input/.select задаёт
   width:100% (для вертикальных форм), здесь его переопределяем. */
.issues-filters .select,
.issues-filters .input,
.performance-filters .select,
.performance-filters .input,
.metric-filters .select {
	width: auto;
	min-width: 128px;
}
/* Текстовый поиск может умеренно растянуться, заполняя ряд. */
.issues-filters .input[type="text"] {
	flex: 1 1 200px;
	min-width: 180px;
	max-width: 380px;
}
/* Единый селектор окна времени: пресеты (<select>) и два поля произвольного
   диапазона стоят одной группой в общей «обойме». Поля дат компактны и
   приглушены — на пресетах они пусты (ввод дат сам включает произвольный
   диапазон) и не должны перетягивать внимание с основного выбора. */
.time-range {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	padding: 3px;
	background: var(--surface-2);
	border: 1px solid var(--border);
	border-radius: var(--radius);
}
/* Пресет-<select> внутри обоймы — без своей рамки, но с фоном обоймы
   (--surface-2), чтобы группа читалась единым контролом. Прозрачный фон здесь
   НЕЛЬЗЯ: нативный дропдаун-попап <option> берёт цвет фона у самого <select>,
   и на transparent браузер рисует его системным белым, а текст остаётся
   светлым (--text) — белое на белом в тёмной теме. Явный фон + color-scheme
   (см. :root) держат попап читаемым в обеих темах. */
.time-range > .select {
	border: 0;
	background: var(--surface-2);
	color: var(--text);
	min-width: 0;
	width: auto;
	font-weight: 500;
}
.time-range-input {
	width: auto;
	min-width: 0;
	max-width: 180px;
	font-size: var(--fs-sm);
	color: var(--text-dim);
	background: var(--surface);
	border: 1px solid var(--border-control);
	border-radius: calc(var(--radius) - 2px);
	padding: 5px 7px;
}
.time-range-input:hover {
	border-color: var(--text-mute);
}
.time-range-input:focus {
	color: var(--text);
	border-color: var(--accent);
	/* Без outline: none — иначе для no-JS-пользователей (эти поля дат ИХ контрол)
	   пропадал бы видимый фокус-ринг (WCAG 2.4.7); наследуем глобальный
	   input:focus { outline: 2px solid var(--accent) }. */
}
.time-range-sep {
	color: var(--text-mute);
}
/* Нативный фолбэк (без JS): поля произвольного диапазона под <details>, чтобы
   на пресетах не висели два пустых поля. Сводка — компактная «Задать даты». */
.time-range-custom { display: inline-flex; }
.time-range-summary {
	font-size: var(--fs-sm);
	color: var(--text-dim);
	cursor: pointer;
	padding: 5px 8px;
	white-space: nowrap;
	user-select: none;
}
.time-range-summary:hover { color: var(--text); }
.time-range-custom[open] > .time-range-summary { color: var(--text); }
.time-range-fields {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	padding: 2px 8px 4px;
}
/* Активный произвольный диапазон: подпись-капсула «с – по» (видимые поля при
   этом пусты, под ввод нового диапазона). Акцентная, чтобы было видно, что
   окно нестандартное. */
.time-range-active {
	font-size: var(--fs-sm);
	font-variant-numeric: tabular-nums;
	color: var(--link);
	background: var(--accent-soft);
	border-radius: var(--radius);
	padding: 3px 8px;
	white-space: nowrap;
}

/* --- Прогрессивное улучшение: единый попап-пикер диапазона (daterange.js) ---
   Когда скрипт улучшил control, он вешает .dr-enhanced: нативные части
   (select/поля/разделитель/подпись) прячем, но НЕ удаляем — они остаются
   источником значений для сабмита формы. Без скрипта .dr-enhanced не ставится
   и всё работает нативно. */
.time-range { position: relative; }
/* В JS-режиме обойма-контейнер лишняя: остаётся один триггер, который несёт
   собственную рамку/фон. Схлопываем хром контейнера, чтобы не было рамки в
   рамке. Нативные части прячем (остаются в DOM как значения формы). */
.dr-enhanced {
	background: transparent;
	border: 0;
	padding: 0;
}
.dr-enhanced > .select,
.dr-enhanced > details,
.dr-enhanced > .time-range-input,
.dr-enhanced > .time-range-sep,
.dr-enhanced > .time-range-active {
	display: none;
}
.dr-trigger {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font: inherit;
	font-size: var(--fs-sm);
	font-variant-numeric: tabular-nums;
	color: var(--text);
	background: var(--surface);
	border: 1px solid var(--border-control);
	border-radius: calc(var(--radius) - 2px);
	padding: 5px 10px;
	cursor: pointer;
}
/* Явный фон на :hover обязателен: базовое button:hover (0,1,1) специфичнее
   .dr-trigger (0,1,0) и иначе перекрашивает триггер в акцент. Даём тонкую
   подсветку surface-2 через .dr-trigger:hover (0,2,0), чтобы победить. */
.dr-trigger:hover { border-color: var(--text-mute); background: var(--surface-2); }
/* Иконки триггера из общего спрайта (как @icon): календарь-аффорданс слева,
   шеврон-вниз справа. Приглушены, чтобы вести подпись, а не спорить с ней. */
.dr-trigger .ic { width: 15px; height: 15px; color: var(--text-dim); flex: none; }
.dr-trigger-label { color: var(--text); }

.dr-popup {
	position: absolute;
	z-index: 40;
	top: calc(100% + 6px);
	left: 0;
	display: flex;
	gap: 12px;
	padding: 12px;
	background: var(--surface);
	border: 1px solid var(--border-control);
	border-radius: var(--radius-lg);
	box-shadow: 0 12px 32px rgba(0, 0, 0, .35);
}
/* На светлой теме глубокая чёрная тень тяжеловата — смягчаем и уводим в синеву
   полотна (оба способа задать светлую тему: явный data-theme и системная). */
:root[data-theme="light"] .dr-popup { box-shadow: 0 10px 28px rgba(20, 30, 50, .14); }
@media (prefers-color-scheme: light) {
	:root:not([data-theme="dark"]) .dr-popup { box-shadow: 0 10px 28px rgba(20, 30, 50, .14); }
}
.dr-popup[hidden] { display: none; }
/* Если двухмесячный попап у правого края вьюпорта — якорь к правому краю
   триггера (см. dr-popup--right в daterange.js), чтобы не уезжал за экран. */
.dr-popup--right { left: auto; right: 0; }

.dr-presets {
	display: flex;
	flex-direction: column;
	gap: 4px;
	padding-right: 12px;
	border-right: 1px solid var(--border-control);
}
.dr-preset {
	font: inherit;
	font-size: var(--fs-sm);
	text-align: left;
	white-space: nowrap;
	color: var(--text-dim);
	background: transparent;
	border: 0;
	border-radius: var(--radius);
	padding: 6px 10px;
	cursor: pointer;
}
.dr-preset:hover { background: var(--surface-2); color: var(--text); }

.dr-cal { display: flex; flex-direction: column; gap: 8px; }
.dr-cal-head { display: flex; align-items: center; gap: 8px; }
.dr-nav {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: var(--text);
	background: var(--surface-2);
	border: 1px solid var(--border-control);
	border-radius: var(--radius);
	width: 28px; height: 28px;
	/* Сброс padding обязателен: глобальное `button` даёт 6px 12px, и внутри
	   28px-квадрата на иконку остаётся 2px — стрелка схлопывается в чёрточку. */
	padding: 0;
	cursor: pointer;
}
.dr-nav .ic { width: 16px; height: 16px; flex: none; }
.dr-nav:hover:not(:disabled) { border-color: var(--text-mute); }
.dr-nav:disabled { opacity: .35; cursor: default; }
.dr-months { display: flex; gap: 16px; flex: 1; }
.dr-month { display: flex; flex-direction: column; gap: 4px; }
.dr-month-title {
	text-align: center;
	font-size: var(--fs-sm);
	font-weight: 600;
	/* Без text-transform: capitalize. Intl отдаёт для ru строку вида
	   «июнь 2026 г.», и capitalize поднимал регистр КАЖДОМУ слову, включая
	   сокращение: получалось «Июнь 2026 Г.». Первую букву поднимаем
	   ::first-letter — она трогает ровно один символ. */
}
.dr-month-title::first-letter {
	text-transform: uppercase;
}
.dr-week-head, .dr-days {
	display: grid;
	grid-template-columns: repeat(7, 30px);
	gap: 2px;
}
.dr-wd {
	text-align: center;
	font-size: var(--fs-label);
	color: var(--text-mute);
	padding: 2px 0;
}
.dr-day {
	font: inherit;
	font-size: var(--fs-sm);
	font-weight: 400; /* базовое button задаёт 600 — дни не должны быть жирными */
	font-variant-numeric: tabular-nums;
	height: 30px;
	color: var(--text);
	background: transparent;
	border: 0;
	border-radius: var(--radius);
	cursor: pointer;
}
.dr-day.dr-empty { visibility: hidden; }
.dr-day:hover:not(.dr-future):not(.dr-empty) { background: var(--surface-2); }
.dr-day.dr-future { color: var(--text-mute); opacity: .4; cursor: default; }
/* «Сегодня» — заметнее, чем тонкая рамка: приглушённое кольцо + полужирный. */
.dr-day.dr-today { box-shadow: inset 0 0 0 1px var(--text-mute); font-weight: 600; }
/* Внутри выбранного диапазона — мягкая акцентная подложка, края — сплошной
   акцент; так интервал читается как единое выделение, а не набор точек. */
/* Полоса «в диапазоне» — единственный сигнал того, какие дни попали в интервал,
   поэтому нужен различимый контраст: --accent-soft (~12% акцента) давал ~1.2:1 и
   на глаз читался как «нет подсветки». Берём заметно более плотный замес акцента
   (~24%) — контраст к поверхности карточки поднимается до различимого. Крышки
   диапазона (.dr-start/.dr-end) остаются сплошным акцентом (ниже). */
.dr-day.dr-in { background: color-mix(in srgb, var(--accent) 34%, transparent); border-radius: 0; }
.dr-day.dr-start, .dr-day.dr-end {
	background: var(--accent);
	color: var(--accent-fg);
}
.dr-day.dr-start { border-top-left-radius: var(--radius); border-bottom-left-radius: var(--radius); }
.dr-day.dr-end { border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius); }
/* Скругляем концы полосы на переносах недель: крайний левый столбец (Пн) и
   крайний правый (Вс) внутри диапазона получают скруглённый внешний кап, иначе
   полоса обрывается прямым углом на границе строки. Сетка .dr-days — ровно 7
   столбцов (пустые lead-ячейки тоже считаются), поэтому nth-child(7n±) точно
   попадает в края строк. */
.dr-days .dr-in:nth-child(7n+1) { border-top-left-radius: var(--radius); border-bottom-left-radius: var(--radius); }
.dr-days .dr-in:nth-child(7n)   { border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius); }

.dr-foot {
	display: flex;
	align-items: center;
	gap: 8px;
	padding-top: 8px;
	border-top: 1px solid var(--border-control);
}
.dr-range-text {
	flex: 1;
	font-size: var(--fs-sm);
	font-variant-numeric: tabular-nums;
	color: var(--text-dim);
}

@media (max-width: 720px) {
	.dr-popup { flex-direction: column; }
	.dr-presets { flex-direction: row; flex-wrap: wrap; border-right: 0; border-bottom: 1px solid var(--border-control); padding-right: 0; padding-bottom: 8px; }
	.dr-months { flex-direction: column; }
}
/* Metric-фильтр: подпись над контролом (label-обёртка) — пары стоят в ряд. */
.metric-filters > label {
	width: auto;
	gap: 4px;
	font-size: var(--fs-sm);
}
/* Собственного правила для контролов фильтров issues больше нет: разметка
   несёт class="select"/"input", база .input/.select уже даёт рамку на
   --border-control, а ширину по содержимому — .issues-filters .select выше. */

/* Metric-фильтр по лейблам (страница метрики). */
.metric-labels {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
}
.metric-label-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	margin-bottom: 4px;
}
.metric-label-key {
	color: var(--text-dim);
	font-size: var(--fs-sm);
	font-weight: 600;
}

.culprit {
	color: var(--text-dim);
	font-size: var(--fs-sm);
}

.bulk-actions {
	display: flex;
	gap: 8px;
	margin-top: 8px;
	/* См. .team-member-add-form: на узком экране «Вернуть в работу» уходила за
	   край во вложенный скроллер. */
	flex-wrap: wrap;
}
.bulk-actions button:not(.btn) {
	background: var(--surface);
	color: var(--text);
	border: 1px solid var(--border-control);
	font-weight: 600;
}
.bulk-actions button:not(.btn):hover {
	background: var(--surface);
	border-color: var(--accent);
}

.pagination {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-top: 12px;
	color: var(--text-dim);
	font-size: var(--fs-base);
	font-variant-numeric: tabular-nums;
}

.issue-meta {
	display: flex;
	/* Перенос обязателен: на 375px три пункта меты в одну строку распирали
	   страницу (раньше это маскировал скроллер .app-content, №82). */
	flex-wrap: wrap;
	gap: 12px;
	margin: 6px 0 12px;
	color: var(--text-dim);
	font-size: var(--fs-base);
}

.issue-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: 8px;
	margin-bottom: 16px;
}
.issue-actions form {
	display: flex;
	gap: 8px;
	margin: 0;
}
/* Рамку и фон селекту назначения даёт база .select (разметка несёт
   class="select"). Базовое .select даёт width:100% для вертикальных форм;
   в строке действий assign-form контрол живёт по содержимому. */
.assign-form .select {
	width: auto;
	min-width: 140px;
}

.chart {
	display: block;
	width: 100%;
	max-width: 480px;
	height: 96px;
	color: var(--accent);
	margin-bottom: 16px;
}

/* Выбор типа проверки в форме монитора: радиокнопки-вкладки + прогрессивное
   раскрытие полей выбранного типа без JS (см. kindPicker в monitorform.templ).
   Радио скрыты визуально, но фокусируемы; клик по .kind-tab переключает. */
.kind-picker {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	align-items: center;
	margin-bottom: 16px;
}
.kind-picker-label {
	flex-basis: 100%;
	font-size: var(--fs-base);
	color: var(--text-dim);
	margin-bottom: 2px;
}
.kind-radio {
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}
.kind-tab {
	padding: 6px 16px;
	border: 1px solid var(--border-control);
	border-radius: var(--radius);
	cursor: pointer;
	font-size: 14px;
	user-select: none;
}
.kind-radio:checked + .kind-tab {
	background: var(--accent);
	color: var(--accent-fg);
	border-color: var(--accent);
}
.kind-radio:focus-visible + .kind-tab {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}
.kind-fields {
	flex-basis: 100%;
	display: none;
}
#kf-http:checked ~ .kind-fields--http,
#kf-tcp:checked ~ .kind-fields--tcp,
#kf-dns:checked ~ .kind-fields--dns,
#kf-heartbeat:checked ~ .kind-fields--heartbeat {
	display: block;
}

/* График частоты событий на странице issue — с осями (viewBox 360×140,
   пропорции сохраняются, поэтому height:auto, а не фикс). */
.chart-freq {
	display: block;
	width: 100%;
	max-width: 480px;
	height: auto;
	color: var(--accent);
	margin-bottom: 16px;
}

/* metric-chart не имел правила размера и рендерился в дефолтном масштабе SVG,
   из-за чего подписи осей были непропорционально крупными. Тянем на ширину
   карточки — тогда viewBox 720 масштабируется предсказуемо (как у остальных). */
.metric-chart {
	display: block;
	width: 100%;
	height: auto;
	margin-bottom: 16px;
	/* Цвет линии и заливки графика: SVG рисует их currentColor, поэтому
	   бренд-акцент задаётся здесь токеном, а не зашитым хексом в Go. */
	color: var(--accent);
}

/* Оси/сетка и подписи графиков: приглушённый цвет, моноширинный мелкий шрифт.
   Пороговые линии — цвет опасности. */
.metric-chart text, .chart-freq text, .latency-chart text, .chart text { font-size: var(--fs-xs); font-family: var(--font-mono); }

/* Скруглённый верх у баров (трафик, гистограмма, частота событий) — мягче и
   современнее, единый язык с остальными графиками. Прозрачные полосы наведения
   (.hover-band) не трогаем; полоска доступности — отдельный класс со своей
   семантикой. rx в координатах viewBox масштабируется вместе с графиком. */
.chart-freq rect:not(.hover-band),
.chart rect:not(.hover-band) { rx: 2px; }
.chart-axis { color: var(--text-dim); }
.chart-threshold { color: var(--danger); }
/* Подпись порога стоит у правого края и на плотном ряде оказывается прямо
   поверх линии данных. Обводка цветом карточки (рисуется ПОД заливкой глифа
   благодаря paint-order) отделяет её от графика, не пряча данные. */
.chart-threshold text {
	paint-order: stroke fill;
	stroke: var(--surface);
	stroke-width: 2px;
	stroke-linejoin: round;
}

/* .issue-detail — вертикальный ритм страницы issue: заголовок, мета, действия,
   график и лента событий идут одной колонкой. Ширину не ограничиваем: потолок
   стоит на самой контентной колонке (.app-content), а второй уровень
   ограничения давал ступеньку — список issues шире, чем таблица событий на
   детали того же issue. */
.issue-detail {
	min-width: 0;
}

/* .issue-chart парен с .card (фон/рамка/радиус/внутренний отступ оттуда) —
   здесь только горизонтальная прокрутка: SVG за 7 дней шире узкого экрана,
   и обрезать его нельзя. */
.issue-chart {
	margin-bottom: 16px;
	overflow-x: auto;
}
/* График событий тянем на всю карточку: .chart/.chart-freq ограничены
   max-width 480px (разумно для узкой врезки в списке), но в карточке детали
   это давало график на половину ширины и пустое поле справа. Высоту не
   задаём — она считается из пропорций холста, иначе браузер вписал бы рисунок
   по высоте и отцентрировал, оставив поля по бокам. */
.issue-chart .chart,
.issue-chart .chart-freq {
	max-width: none;
	height: auto;
}

.issue-events-heading {
	margin: 20px 0 8px;
}

.event-trace-link {
	margin: 6px 0 12px;
	font-size: var(--fs-base);
}

/* .tags парен с .data-table: таблица тегов — это узкий список ключ/значение,
   и растягивать её на всю ширину незачем. Значение моноширинное, как и
   остальные машинные строки (id, стектрейсы). */
.tags {
	width: auto;
	min-width: min(420px, 100%);
	margin-bottom: 12px;
}
.tags td:first-child {
	color: var(--text-dim);
	white-space: nowrap;
}
.tags td:last-child {
	font-family: var(--font-mono);
	word-break: break-all;
}

.issue-events {
	margin-bottom: 16px;
}
.event-row {
	cursor: default;
}
.event-row-selected {
	background: var(--accent-soft);
}

.event-detail {
	border-top: 1px solid var(--border);
	padding-top: 12px;
}
.event-detail h3 {
	font-size: var(--fs-base);
	text-transform: uppercase;
	color: var(--text-dim);
	margin: 12px 0 6px;
}
.event-detail pre {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 8px;
	overflow-x: auto;
	font-size: var(--fs-base);
}
.event-meta {
	display: grid;
	grid-template-columns: 1fr;
	gap: 0 24px;
}
@media (min-width: 700px) {
	.event-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Пользователь/SDK — это ОТОБРАЖАЕМЫЕ значения, а не поля ввода. Общий
   .event-detail pre одевает их в рамку+фон+padding (уместно для JSON/стека),
   отчего блок читался как редактируемый input. Здесь снимаем «поле» и
   оставляем моно-значение под заголовком — тот же язык, что у блока «Теги».
   Специфичность (0,2,1) осознанно выше .event-detail pre (0,1,1), чтобы
   правило выигрывало независимо от порядка. */
.event-detail .event-meta pre {
	background: none;
	border: 0;
	border-radius: 0;
	padding: 2px 0;
	color: var(--text);
	white-space: pre-wrap;
	word-break: break-word;
}

.frames {
	display: flex;
	flex-direction: column;
	gap: 6px;
}
.frame {
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 6px 10px;
	font-size: var(--fs-base);
}
.frame.in-app {
	border-color: var(--accent);
	background: var(--accent-soft);
}
.frame-location {
	color: var(--text-dim);
	font-size: var(--fs-sm);
	overflow-wrap: anywhere;
}
.frame-function {
	font-weight: 600;
	overflow-wrap: anywhere;
}
.error-page {
	max-width: 460px;
	margin: 72px auto;
	padding: 0 16px;
	text-align: center;
}
/* Код статуса — герой: моноширинный, в акценте, обрамлён скобками-рамкой
   [код] (тот же «пойман в рамку» мотив, что и у фирменного знака). */
.error-code {
	font-family: var(--font-mono);
	font-size: 66px;
	font-weight: 700;
	line-height: 1;
	color: var(--link);
	margin: 0 0 10px;
}
.error-code::before {
	content: "[";
	color: var(--text-mute);
	margin-right: 10px;
}
.error-code::after {
	content: "]";
	color: var(--text-mute);
	margin-left: 10px;
}
.error-title {
	font-size: 22px;
	font-weight: 700;
	margin: 0 0 6px;
	color: var(--text);
}
.error-body {
	max-width: 380px;
	margin: 0 auto 20px;
	color: var(--text-dim);
	font-size: 14px;
	line-height: 1.6;
}

.frame-module {
	color: var(--text-dim);
	font-size: var(--fs-sm);
}
/* Стектрейс — это код: фреймы моноширинным. Свёрнутые system-фреймы
   (<details>) кликабельны; длинный id события переносится. */
.frame {
	font-family: var(--font-mono);
}
.frame-system > summary {
	cursor: pointer;
	overflow-wrap: anywhere;
}
/* Исходный код кадра: строки с номерами, строка ошибки подсвечена акцентом. */
.frame-source {
	margin-top: 6px;
	padding-top: 6px;
	border-top: 1px solid var(--border);
	overflow-x: auto;
	font-size: var(--fs-sm);
	line-height: 1.55;
}
.src-line {
	display: flex;
}
.src-line-current {
	background: var(--accent-soft);
}
.src-lineno {
	flex: 0 0 auto;
	width: 44px;
	padding-right: 12px;
	text-align: right;
	color: var(--text-mute);
	user-select: none;
}
.src-line-current .src-lineno {
	color: var(--link);
	font-weight: 700;
}
.src-code {
	flex: 1 1 auto;
	white-space: pre;
}
/* Локальные переменные кадра (если SDK их прислал). */
.frame-vars {
	margin-top: 6px;
	padding-top: 6px;
	border-top: 1px dashed var(--border);
}
.frame-vars-title {
	margin-bottom: 4px;
	font-size: var(--fs-sm);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--text-dim);
}
/* Ссылка-переключатель «показать/свернуть системные кадры» (серверно, без JS). */
.frames-toggle {
	margin: 0 0 6px;
	font-size: var(--fs-sm);
}
/* Контексты — читаемый отформатированный JSON (json.Indent), а не одна строка. */
.contexts-json {
	white-space: pre;
	overflow-x: auto;
}
/* Карточка «Запрос» на детали issue (Sentry request-интерфейс: строка
   METHOD URL, параметры, тело, заголовки). */
.request-line {
	margin: 0 0 8px;
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
	overflow-wrap: anywhere;
}
.request-method {
	font-weight: 700;
	color: var(--link);
	margin-right: 6px;
}
.request-view .ctx-title:first-of-type {
	margin-top: 0;
}
/* Структурные контексты: карточки-группы (os/runtime/trace…) и Request. */
.ctx-groups {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
	gap: 10px;
	margin-bottom: 12px;
}
.ctx-card {
	padding: 10px 12px;
}
.ctx-title {
	margin: 0 0 6px;
	font-size: var(--fs-sm);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--text-dim);
}
.ctx-table td {
	vertical-align: top;
	padding: 2px 8px 2px 0;
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
}
.ctx-key {
	color: var(--text-dim);
	white-space: nowrap;
}
.ctx-val {
	overflow-wrap: anywhere;
}
.raw-json > summary {
	cursor: pointer;
	color: var(--text-dim);
	font-size: var(--fs-sm);
	margin-bottom: 6px;
}
/* Breadcrumbs — таймлайн следов до ошибки (лог/HTTP/навигация). */
.breadcrumbs {
	list-style: none;
	margin: 0 0 12px;
	padding: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	overflow: hidden;
}
.bc-item {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 8px;
	padding: 5px 10px;
	border-top: 1px solid var(--border);
	font-size: var(--fs-sm);
}
.bc-item:first-child {
	border-top: none;
}
.bc-cat {
	flex: 0 0 auto;
	min-width: 120px;
	color: var(--text-dim);
	font-family: var(--font-mono);
}
.bc-level {
	flex: 0 0 auto;
	font-size: 11px;
	text-transform: uppercase;
	color: var(--text-mute);
}
.bc-level-error,
.bc-level-fatal {
	/* Без фолбэка: токен определён в обеих темах, а значение в фолбэке от него
	   отличалось — тихая ловушка при будущей правке палитры. */
	color: var(--danger);
}
.bc-level-warning {
	color: var(--warn);
}
.bc-msg {
	flex: 1 1 200px;
	overflow-wrap: anywhere;
}
.bc-data {
	flex-basis: 100%;
	margin: 4px 0 0;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
	color: var(--text-dim);
	font-size: var(--fs-sm);
}
.event-id {
	overflow-wrap: anywhere;
	margin-bottom: 8px;
}

.monitors table, .monitor-detail table {
	margin-bottom: 16px;
}
.availability-bars {
	display: inline-block;
	width: 192px;
	height: 24px;
	vertical-align: middle;
}
.monitor-meta {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin: 6px 0 12px;
	color: var(--text-dim);
	font-size: var(--fs-base);
}
.monitor-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
	margin-bottom: 16px;
}
.monitor-actions form {
	display: flex;
	gap: 8px;
	margin: 0;
}
.monitor-actions button:not(.btn) {
	background: var(--surface);
	color: var(--text);
	border: 1px solid var(--border-control);
	font-weight: 600;
}
.monitor-actions button:not(.btn):hover {
	background: var(--surface);
	border-color: var(--accent);
}
.monitor-chart {
	margin-bottom: 16px;
}
.latency-chart {
	display: block;
	width: 100%;
	max-width: 480px;
	height: auto;
	/* Заливка под линией p50 рисуется currentColor (см. writeLineWithArea):
	   без этого свойства она наследовала цвет текста и выходила серой, тогда
	   как сама линия красится классом .series-p50 в акцент. */
	color: var(--accent);
}

/* Стек задержек на детали монитора занимает карточку целиком: широкий viewBox
   (720×160 ≈ 4.5:1) даёт крупные читаемые столбики без пустого поля справа.
   Пропорции держит viewBox (height:auto). */
.monitor-chart .latency-chart {
	width: 100%;
	max-width: none;
	height: auto;
}

/* Публичная статус-страница (/status/{slug}): свой layout, без шапки
   приложения — отсюда собственные отступы у main. */
.status-page {
	max-width: 720px;
	margin: 0 auto;
	padding: 24px 20px;
}
.status-overall {
	display: inline-block;
	padding: 8px 16px;
	border-radius: var(--radius-lg);
	font-weight: 600;
	margin: 6px 0 20px;
}
.status-overall-ok { background: color-mix(in srgb, var(--good) 16%, transparent); color: var(--good-ink); }
.status-overall-partial { background: color-mix(in srgb, var(--partial) 16%, transparent); color: var(--partial-ink); }
.status-overall-major { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger-ink); }
.status-description {
	color: var(--text-dim);
}
.status-tiles {
	list-style: none;
	padding: 0;
	margin: 0 0 20px;
}
/* .status-tile pairs with the shared .card component (background/border/
   radius/padding) in the template; only the tile-specific spacing lives
   here. */
.status-tile {
	margin-bottom: 8px;
}
.status-tile-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	margin-bottom: 6px;
}
.status-tile-name {
	font-weight: 600;
}
.status-tile-uptime {
	color: var(--text-dim);
	font-size: var(--fs-base);
	margin-top: 6px;
	font-variant-numeric: tabular-nums;
}
.status-tile-bars .availability-bars {
	width: 100%;
	height: 24px;
}

/* Настройки статус-страниц проекта: заголовок страницы и группа карточек
   (настройки, сервисы, публикация). Раньше поля шли сплошным потоком, а
   список сервисов был набором строк с повторяющейся подписью «Публичное имя»
   и полями, начинавшимися с разного отступа, — теперь это таблица. */
.status-page-item {
	margin-bottom: 16px;
}
/* Три блока одной карточки статус-страницы — друг под другом: настройки,
   сервисы, публикация. Разделены горизонтальными линиями, чтобы группы
   читались как части одной формы, а не как отдельные карточки. */
/* Настройки статус-страниц: одна страница = одна узкая карточка в settings-grid,
   прижатая влево с пустой правой половиной (UI-аудит §2). Публичное live-превью
   справа невозможно — публичная страница отдаёт X-Frame-Options: DENY, а
   ослаблять защиту от кликджекинга ради превью нельзя. Поэтому по принципу
   гибрида падаем в дефолт: центрируем колонку комфортной ширины, карточки — в
   один столбец (их обычно немного). */
.status-pages {
	max-width: 760px;
	margin-inline: auto;
}
.status-pages .settings-grid {
	grid-template-columns: 1fr;
}

.status-page-editor {
	display: flex;
	flex-direction: column;
}
.status-page-block {
	min-width: 0;
	padding: 12px 0;
}
.status-page-block:first-child {
	padding-top: 0;
}
.status-page-block:last-child {
	padding-bottom: 0;
}
.status-page-block + .status-page-block {
	border-top: 1px solid var(--border);
}
.status-page-block > h3 {
	margin-top: 0;
	color: var(--text-dim);
	text-transform: uppercase;
	font-size: var(--fs-label);
	letter-spacing: .4px;
}
/* Поля настроек и публичных имён не растягиваем на всю карточку: slug,
   заголовок и публичное имя — короткие значения, поле во всю ширину под них
   выглядит несоразмерно. */
/* Внутри узкой карточки (сетка 3/2/1) фиксированные ширины полей больше не
   нужны — поля занимают ширину своего блока. Таблица сервисов тоже на всю
   ширину карточки. */
.status-page-editor .data-table {
	width: 100%;
}

/* OAuth / social login: кнопки на /login, /register и в профиле. */
.oauth-buttons {
	margin-top: 12px;
	display: flex;
	flex-direction: column;
	gap: 8px;
}
.oauth-sep {
	text-align: center;
	color: var(--text-dim);
	margin: 6px 0;
}
/* Профиль: список связанных аккаунтов. */
.linked-email {
	color: var(--text-dim);
	font-size: var(--fs-base);
}
.linked-note {
	color: var(--text-dim);
	font-size: var(--fs-base);
}
form.inline {
	display: inline;
}

/* ===== Design-system components (sub-project ②) ===== */
/* Общие переиспользуемые классы для app-shell (③) и разделов (④).
   Все цвета/отступы — через токены из :root выше; работает в обеих темах. */

/* -- Buttons -- */
.btn {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 6px 12px;
	border: none;
	border-radius: var(--radius);
	font-family: inherit;
	font-size: var(--fs-base);
	font-weight: 600;
	line-height: 1.4;
	cursor: pointer;
	text-decoration: none;
}
.btn-primary {
	background: var(--accent);
	color: var(--accent-fg);
}
.btn-primary:hover {
	background: var(--accent-hover);
}
.btn-ghost {
	background: transparent;
	color: var(--text);
	border: 1px solid var(--border-control);
}
.btn-ghost:hover {
	background: var(--surface-2);
}
.btn-danger {
	background: var(--danger);
	color: var(--accent-fg);
}
.btn-danger:hover {
	filter: brightness(0.92);
}
/* Заливка кнопки в ТЁМНОЙ теме отличается от токена --danger: сам токен (#f0574a)
   под белой подписью даёт 3.41:1, а это сплошная кнопка с текстом — нужно 4.5.
   #cc4a3f даёт 4.55. В светлой теме --danger под белым проходит и так, поэтому
   переопределение только для тёмной. */
:root:not([data-theme="light"]) .btn-danger {
	background: #cc4a3f;
	border-color: #cc4a3f;
}
@media (prefers-color-scheme: light) {
	:root:not([data-theme="dark"]) .btn-danger {
		background: var(--danger);
		border-color: var(--danger);
	}
}
/* .btn-danger-ghost — опасное действие В СТРОКЕ таблицы (удалить участника,
   канал, ключ, пробу). Сплошная заливка тут не годится: строк много, и
   столбик красных прямоугольников перетягивает на себя всё внимание, хотя
   удаление — не то действие, к которому надо вести взгляд. Заливку
   оставляем одиночным кнопкам в «опасной зоне». */
.btn-danger-ghost {
	background: none;
	color: var(--danger);
	border-color: color-mix(in srgb, var(--danger) 40%, transparent);
}
.btn-danger-ghost:hover {
	background: color-mix(in srgb, var(--danger) 12%, transparent);
	color: var(--danger);
}
/* .row-actions — действия в строке таблицы, когда их больше одного: правка
   лежит ссылкой (модалка на CSS :target), удаление — формой с POST, и без
   обёртки форма своим display:block роняла бы ссылку на строку выше.
   flex-wrap — на узком экране, где столбец действий сжимается. */
.row-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: calc(var(--space) * 2);
}
.row-actions form {
	margin: 0;
}
.btn:disabled {
	opacity: .5;
	cursor: not-allowed;
	filter: none;
}

/* -- Badges --
   Базовый .badge (display/radius/padding/uppercase) уже определён выше;
   здесь только семантические модификаторы новой палитры + служебные варианты.
   .badge-info уже существует выше и совпадает по стилю — переиспользуется как есть. */
.badge-good { background: color-mix(in srgb, var(--good) 16%, transparent); color: var(--good-ink); }
.badge-warn { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn-ink); }
.badge-danger { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger-ink); }
.badge-neutral {
	background: var(--surface-2);
	color: var(--text-dim);
}

/* -- Data table --
   Отдельный класс поверх глобальных table/th/td: компактнее, с hover-строками
   и колонкой .num для чисел. Не конфликтует с существующими table/th/td —
   выше по специфичности при использовании внутри .data-table. */
/* По умолчанию таблица занимает ширину своего контейнера: у таблиц с гибкой
   основной колонкой (issue, эндпойнт, монитор) слабину забирает эта колонка.
   Раньше умолчанием было width:auto — таблица льнула к содержимому и
   оставляла справа пустое поле внутри и без того ограниченной колонки. */
.data-table {
	width: 100%;
	max-width: 100%;
	border-collapse: collapse;
	font-variant-numeric: tabular-nums;
}
/* Обратный модификатор — для узких таблиц «ключ → значение» (теги события и
   т.п.), которые растягивать через всю страницу незачем. */
.data-table--auto {
	width: auto;
}
.data-table th {
	text-align: left;
	padding: 0 8px 8px 0;
	border-bottom: 1px solid var(--border);
	color: var(--text-dim);
	font-size: var(--fs-label);
	font-weight: 600;
	text-transform: uppercase;
}
/* Индикатор сортировки. Стрелка рисуется по aria-sort через ::after, поэтому
   направление (для глаза) и семантика (для скринридера, читающего aria-sort) —
   из одного источника; глиф декоративен. Активная колонка подсвечивается
   акцентным цветом текста, чтобы «по какой колонке сортировка» читалось сразу. */
.data-table th[aria-sort]:not([aria-sort="none"]) { color: var(--link); }
.data-table th[aria-sort="ascending"] a::after { content: " \25B4"; font-size: 0.85em; }
.data-table th[aria-sort="descending"] a::after { content: " \25BE"; font-size: 0.85em; }
.data-table td {
	padding: 8px 8px 8px 0;
	border-bottom: 1px solid var(--border);
	font-size: var(--fs-sm);
	font-variant-numeric: tabular-nums;
}
.data-table tbody tr:hover {
	background: var(--surface-2);
}
.data-table .num {
	text-align: right;
	font-variant-numeric: tabular-nums;
}

/* -- Stat tile -- */
.stat-tile {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	padding: 10px 12px;
}
.stat-label {
	color: var(--text-dim);
	font-size: var(--fs-label);
	font-weight: 600;
	letter-spacing: .3px;
	text-transform: uppercase;
}
.stat-value {
	margin-top: 2px;
	font-size: 20px;
	font-weight: 680;
	letter-spacing: -.5px;
	font-variant-numeric: tabular-nums;
	overflow-wrap: anywhere;
}

/* Кнопка отправки не должна прилипать к предыдущему блоку. В форме квот
   («Использование и лимиты») кнопка «Сохранить» шла сразу за таблицей, в форме
   SSO — сразу за строкой-чекбоксом «Принудительный вход»: вплотную, без
   воздуха. Задаём верхний отступ прямым submit-кнопкам этих форм (в остальных
   формах перед кнопкой идёт .field со своим отступом — там правка не нужна). */
.org-quota-form > button[type="submit"],
.sso-form > button[type="submit"] {
	margin-top: 16px;
}

/* -- Cards / panels -- */
.card,
.panel {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	padding: 14px 16px;
}
.card-header {
	margin-bottom: 10px;
	font-weight: 650;
}
/* .card-header-action — действие в заголовке карточки (переименовать команду).
   Прижато вправо, чтобы название и его идентификатор читались слитно, а кнопка
   не разрывала строку заголовка. Кегль обычный: заголовок крупный, и кнопка,
   унаследовав его размер, спорила бы с самим названием. */
.card-header:has(.card-header-action) {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: calc(var(--space) * 2);
}
.card-header-action {
	margin-left: auto;
	font-size: var(--fs-sm);
	font-weight: 500;
}

/* -- Адаптивная сетка блоков настроек (settings/profile/teams/org) --
   1 колонка → 2 (≥1024px) → 3 (≥1440px). minmax(0,1fr) не даёт широкому
   содержимому (таблицы, <pre>) распирать колонку. Карточки с широкими
   таблицами занимают всю ширину строки через .card--wide. */
.settings-grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: 16px;
	align-items: start;
	margin-bottom: 16px;
}
@media (min-width: 1024px) {
	.settings-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1440px) {
	.settings-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.settings-grid > .card { margin: 0; }
.settings-grid > .card--wide { grid-column: 1 / -1; }

/* Настройки проекта: карточки одной строки — равной высоты (ровные низа
   вместо «лесенки», которую давал общий align-items:start). */
.project-settings .settings-grid { align-items: stretch; }
/* Вертикальная пара карточек в одной колонке (Хранение данных + Опасная зона).
   Занимает одну ячейку сетки и НЕ растягивается под высокую соседнюю карточку
   (Регрессии) — align-self:start прижимает пару к верху строки. */
.settings-stack {
	display: flex;
	flex-direction: column;
	gap: 16px;
	align-self: start;
}
.settings-stack > .card { margin: 0; }
/* «Новый ключ» не должен прилипать к таблице DSN-ключей сверху. */
.key-create-form { margin-top: 12px; }

/* Разлипание вертикально составленных действий в карточке: несколько форм/
   кнопок подряд (напр. Опасная зона — «Покинуть» и «Удалить» шли впритык). */
.card-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-top: 4px;
}
.card-actions > form {
	margin: 0;
}

/* -- About page (/about): build-info list + outbound links -- */
.about-list {
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: 6px 16px;
	margin: 0;
}
.about-list dt { color: var(--text-dim); }
.about-list dd {
	margin: 0;
	color: var(--text);
	font-variant-numeric: tabular-nums;
}
.about-links {
	margin-top: 16px;
	color: var(--text-dim);
}
.about-links a {
	color: var(--link);
	text-decoration: none;
}
.about-links a:hover { text-decoration: underline; }

/* -- Tabs -- */
.tabs {
	display: flex;
	gap: 4px;
	/* Линия под вкладками отделяет активную вкладку от содержимого — это
	   граница контрола, а не разделитель карточек. */
	border-bottom: 1px solid var(--border-control);
	/* На узком экране ряд вкладок шире вьюпорта — прокручивается сам, а не
	   распирает страницу (раньше это маскировал скроллер .app-content, №82).
	   tabindex не нужен: вкладки-ссылки фокусируемы, Tab докручивает ряд. */
	overflow-x: auto;
}
.tab {
	padding: 8px 10px;
	background: none;
	border: none;
	color: var(--text-dim);
	font-size: var(--fs-base);
	font-weight: 600;
	cursor: pointer;
}
.tab:hover {
	color: var(--text);
}
.tab.is-active {
	color: var(--text);
	box-shadow: inset 0 -2px 0 var(--accent);
}

/* -- Filter chips / pills -- */
.chip {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	background: var(--surface-2);
	/* Чип — интерактивный элемент, его граница обязана добирать 3:1. */
	border: 1px solid var(--border-control);
	border-radius: 999px;
	padding: 3px 10px;
	color: var(--text-dim);
	font-size: var(--fs-sm);
	cursor: pointer;
}
.chip.is-active {
	background: var(--accent-soft);
	border-color: var(--link);
	color: var(--link);
}

/* -- Breadcrumb -- */
.breadcrumb {
	display: flex;
	align-items: center;
	gap: 6px;
	color: var(--text);
	font-size: var(--fs-base);
	font-weight: 600;
	/* Крошка — единственный гибкий элемент topbar: именно она уступает место на
	   узких экранах, обрезаясь многоточием, а не переключатели языка и темы,
	   которые обязаны оставаться нажимаемыми. */
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
/* Ссылка «назад» несёт иконку из спрайта (см. breadcrumbBack), поэтому сама
   выкладывается флексом: иконка и подпись должны стоять на одной базовой
   линии с одинаковым зазором на всех страницах-деталях. */
.breadcrumb a {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	color: var(--text-dim);
}
.breadcrumb a:hover {
	color: var(--text);
}

/* -- Empty state -- */
.empty-state {
	padding: 40px 20px;
	color: var(--text-dim);
	text-align: center;
}
.empty-state .ic {
	display: block;
	width: 28px;
	height: 28px;
	margin: 0 auto 10px;
	color: var(--text-mute);
}
.empty-state h3,
.empty-state .empty-title {
	margin: 0 0 4px;
	color: var(--text);
	font-size: 14px;
	font-weight: 650;
}
.empty-state p {
	margin: 0 auto;
	max-width: 380px;
	font-size: var(--fs-base);
	line-height: 1.5;
}
.empty-state .empty-cta {
	margin-top: 14px;
}
.no-projects {
	text-align: center;
	color: var(--text-dim);
}

/* -- Compact forms -- */
.field {
	display: block;
	margin-bottom: 12px;
}
.field label {
	display: flex;
	flex-direction: column;
	gap: 4px;
	color: var(--text-dim);
	font-size: var(--fs-sm);
}

/* -- Двухколоночная сетка полей внутри форм создания/редактирования
   (монитор, metric-алерт, обслуживание, статус-страница). 1 колонка →
   2 при ≥720px. Поля-исключения (textarea, длинные URL) занимают всю
   используется — там формы остаются одноколоночными. -- */
.form-grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: 0 16px;
	align-items: start;
}
@media (min-width: 720px) {
	.form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.input,
.select {
	display: block;
	width: 100%;
	background: var(--surface);
	border: 1px solid var(--border-control);
	border-radius: var(--radius);
	color: var(--text);
	padding: 6px 10px;
	font-family: inherit;
	font-size: var(--fs-base);
}
.input:focus,
.select:focus {
	outline: 2px solid var(--accent);
	outline-offset: -1px;
}
textarea.input {
	resize: vertical;
	min-height: 64px;
}

/* -- Icon sprite (CSP: default-src 'self' без unsafe-inline запрещает
   style="", поэтому скрываем классом, не инлайн-стилем) -- */
.icon-sprite {
	display: none;
}

/* -- Icon base -- */
.ic {
	width: 1em;
	height: 1em;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: round;
	stroke-linejoin: round;
	vertical-align: -0.125em;
}

/* -- Keyboard focus ring (shared across interactive components) -- */
a:focus-visible, button:focus-visible, .btn:focus-visible, summary:focus-visible,
.rail-area:focus-visible, .rail-logout-btn:focus-visible, .ctx-item:focus-visible,
.tab:focus-visible, .chip:focus-visible, .lang-switcher button:focus-visible,
.theme-toggle button:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

/* -- Help panel (helpPanel, help.templ) -- контекстная справка под <h1>
   страниц-хабов (issues/performance/metrics/uptime/alerts). Нативный
   <details>/<summary> (CSP: default-src 'self' без unsafe-inline не
   допускает JS-раскрытие), свёрнут по умолчанию (нет open) -- тот же приём,
   что .proj-switch/.mobile-nav-summary выше. */
.help-panel {
	margin: 10px 0 16px;
}
.help-panel summary {
	list-style: none;
	cursor: pointer;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 4px 0;
	color: var(--text-dim);
	font-size: var(--fs-base);
	font-weight: 600;
}
.help-panel summary::-webkit-details-marker {
	display: none;
}
.help-panel summary:hover {
	color: var(--text);
}
.help-panel-body {
	margin-top: 6px;
	padding: 10px 12px;
	background: var(--surface-2);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	font-size: var(--fs-base);
	color: var(--text-dim);
	max-width: 640px;
}
.help-panel-body p {
	margin: 0 0 8px;
}
.help-panel-body a {
	color: var(--link);
	text-decoration: none;
}
.help-panel-body a:hover {
	text-decoration: underline;
}

/* -- Getting started checklist (gettingStartedChecklist, help.templ) --
   компактная карточка над списком issues (задача 5, docs-onboarding),
   пока не закрыты все 4 шага онбординга. Список без маркеров: done-шаги
   приглушены (текстом ✓, без зачёркивания -- это не «отменено», а
   «выполнено»), todo-шаги ведут акцентной ссылкой на соответствующую
   страницу (SDK/оповещения/участники). */
.getting-started {
	max-width: 640px;
	margin: 0 0 16px;
}
.getting-started .card-header {
	font-size: 14px;
	font-weight: 650;
}
.gs-steps {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 4px;
	font-size: var(--fs-base);
}
.gs-steps li {
	padding: 2px 0;
}
.gs-done {
	color: var(--text-dim);
}
.gs-todo {
	color: var(--text);
}
.gs-todo a {
	color: var(--link);
	font-weight: 600;
	text-decoration: none;
}
.gs-todo a:hover {
	text-decoration: underline;
}

/* -- Docs index (/docs) -- переиспользует .card/.settings-grid: одна
   секция (Начало/Разделы/Интеграции) на карточку. */
.docs-index {
	display: flex;
	flex-direction: column;
	gap: 16px;
}
/* Группы-разделы — адаптивная сетка карточек, а не одна колонка слева с пустой
   правой половиной (UI-аудит §2). auto-fill заполняет доступную ширину: три
   группы (Начало/Разделы/Интеграции) встают в ряд на широком экране и в одну
   колонку на узком. Контекстный сайдбар остаётся быстрым переходником, а этот
   индекс — визуальной посадочной с группировкой. */
.docs-index-groups {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
	gap: 16px;
	align-items: start;
}
.docs-index-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 6px;
}
.docs-index-list a {
	color: var(--link);
	text-decoration: none;
	font-size: var(--fs-base);
}
.docs-index-list a:hover {
	text-decoration: underline;
}

/* -- Doc content (/docs/{slug}) -- типографика для отрендеренного markdown
   (goldmark + GFM, см. internal/docs). max-width держит строку читабельной,
   line-height ~1.6 — то же, что и остальной текстовый контент кабинета. */
.doc-content {
	max-width: 820px;
	font-size: 14px;
	line-height: 1.6;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	padding: 24px 32px;
}
.doc-content h1 {
	margin: 0 0 16px;
	font-size: 24px;
	font-weight: 700;
	letter-spacing: -.3px;
}
.doc-content h2 {
	margin: 28px 0 10px;
	font-size: 18px;
	font-weight: 650;
	border-bottom: 1px solid var(--border);
	padding-bottom: 6px;
}
.doc-content h3 {
	margin: 20px 0 8px;
	font-size: 15px;
	font-weight: 650;
}
.doc-content p {
	margin: 0 0 12px;
	line-height: 1.6;
}
.doc-content ul,
.doc-content ol {
	margin: 0 0 12px;
	padding-left: 22px;
	line-height: 1.6;
}
.doc-content li + li {
	margin-top: 2px;
}
.doc-content a {
	color: var(--link);
}
.doc-content code {
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
	background: var(--surface-2);
	border: 1px solid var(--border);
	border-radius: 4px;
	padding: 1px 5px;
	/* Инлайновый код в абзаце (имена переменных, пути) длиннее узкого экрана
	   рвётся в любом месте, а не распирает страницу — у него, в отличие от
	   <pre>, нет своего скролла. */
	overflow-wrap: anywhere;
}
.doc-content pre {
	margin: 0 0 14px;
	padding: 12px 14px;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	overflow-x: auto;
}
.doc-content pre code {
	background: none;
	border: none;
	padding: 0;
	font-size: var(--fs-sm);
}
/* Прокрутка широких справочных таблиц (переменные окружения — самая широкая,
   до 1740px при карточке в 818) живёт на обёртке .table-scroll, которую
   рендерит docsTableRenderer (internal/docs) — та же обёртка, что у таблиц
   продукта (scrollRegion). Прежний display:block на самой таблице ломал её
   семантику: таблица с display:block теряет role=table в дереве доступности,
   и скринридер читал справочник как поток строк (№75). */
.doc-content table {
	margin: 0;
	border-collapse: collapse;
	font-size: var(--fs-base);
}
.doc-content .table-scroll {
	margin: 0 0 14px;
}
/* Первая колонка справочных таблиц — имена переменных: не даём длинному
   GOTCHA_EXTERNAL_CHANNEL_DETAILS растягивать таблицу вдвое. */
.doc-content td:first-child {
	white-space: nowrap;
}
.doc-content th {
	text-align: left;
	padding: 6px 8px;
	border-bottom: 1px solid var(--border);
	color: var(--text-dim);
	font-size: var(--fs-xs);
	font-weight: 600;
	text-transform: uppercase;
}
.doc-content td {
	padding: 6px 8px;
	border-bottom: 1px solid var(--border);
}
.doc-content blockquote {
	margin: 0 0 12px;
	padding: 6px 12px;
	border-left: 3px solid var(--border);
	color: var(--text-dim);
}
.doc-content hr {
	margin: 20px 0;
	border: none;
	border-top: 1px solid var(--border);
}

/* ===== Многоцветные графики: цвет из токенов (под-проект ④) =====
   Одноцветные графики наследуют currentColor от своего класса, но там, где
   в одном SVG нужно несколько цветов (полоска доступности, waterfall,
   перцентили, стек задержек), цвет назначается здесь по классу элемента.
   Раньше эти значения были впечатаны в SVG hex-литералами из тёмной палитры
   и не менялись в светлой теме. */
.availability-bars .bar-up      { fill: var(--bar-up); }
/* «Частично» — через --bar-partial, отдельный и от текстового --partial, и от
   --warn: заливкам полоски нужна разводка по светлоте между собой (№28,
   TestAvailabilityBarFillTokens), а текстовые статус-токены прижаты к 4.5:1
   на подложке и разводке не подлежат. */
.availability-bars .bar-partial { fill: var(--bar-partial); }
.availability-bars .bar-down    { fill: var(--bar-down); }
.availability-bars .bar-empty   { fill: var(--bar-empty); }
/* Штрих паттерна «частично» — цветом карточки: полосы «прорезают» заливку и
   читаются одинаково в обеих темах (второй канал состояния, №28). */
.availability-bars .bar-hatch   { fill: var(--surface); }

.waterfall .wf-ok  { fill: var(--accent); }
.waterfall .wf-err { fill: var(--danger); }
/* Скруглённые span-бары — единый язык с остальными графиками. */
.waterfall rect { rx: 2px; }

.latency-chart .series-p50 { stroke: var(--accent); }
/* p95 — бренд-фиолетовый (#7c5cff), не --warn: коричневый как ЦВЕТ ДАННЫХ
   выглядел грязно и ложно читался как «предупреждение». --warn оставлен за
   порогами. p50-синий + p95-фиолетовый — та же бренд-пара, что в градиенте. */
.latency-chart .series-p95 { stroke: #7c5cff; }

/* Фазы запроса — секвенция по бренд-градиенту #3d7bff→#7c5cff. Снизу вверх =
   порядок во времени (DNS→TCP→TLS→TTFB), тёмный низ → светлый верх: цвет сам
   кодирует последовательность фаз, а не набор произвольных категорий. Каждый
   столбик — миниатюра «путешествия» запроса. */
.latency-chart .seg-dns     { fill: var(--phase-dns); }
.latency-chart .seg-connect { fill: var(--phase-tcp); }
.latency-chart .seg-tls     { fill: var(--phase-tls); }
.latency-chart .seg-ttfb    { fill: var(--phase-ttfb); }
/* Метка выброса (час выше шкалы: медленно/таймаут). Цветом события, не фазы. */
.latency-chart .seg-cap     { fill: var(--danger); }
/* Кегль подписей осей задаётся В ЕДИНИЦАХ viewBox, а на экране равен
   font-size × ширинаКарточки / ширинаViewBox. Значит один и тот же font-size
   даёт разный размер у графиков с разным viewBox, а их в продукте два.

   Раньше здесь стояло безусловное 8px с комментарием «viewBox масштабируется
   ~1.5×» — верно только около 1440 и только для viewBox 720. Измерено на живых
   страницах ДО правки: 2.6px при 375, 6.6px при 768, 7.2px при 1024.

   Селектор идёт по классу chart-vb<ширина>, который проставляет сам генератор
   (svgRoot): новый график получает нужный кегль по построению, а не по памяти
   автора. Значения для 1200 = значения для 720 × 1200/720. Порядок правил
   строго по возрастанию min-width — они перекрывают друг друга. */
.chart-vb720 text { font-size: 34px; }
.chart-vb1200 text { font-size: 57px; }

@media (min-width: 700px) {
	.chart-vb720 text { font-size: 15px; }
	.chart-vb1200 text { font-size: 25px; }
}

@media (min-width: 1000px) {
	.chart-vb720 text { font-size: 13px; }
	.chart-vb1200 text { font-size: 22px; }
}

@media (min-width: 1300px) {
	.chart-vb720 text { font-size: 10px; }
	.chart-vb1200 text { font-size: 17px; }
}

@media (min-width: 1700px) {
	.chart-vb720 text { font-size: 8px; }
	.chart-vb1200 text { font-size: 13px; }
}

/* ===== Performance / Trace / Profiles: секционные классы (под-проект ④) =====
   Эти классы стояли в разметке без определений, из-за чего блоки на страницах
   endpoint detail, perf issue detail, trace и profiles рендерились без
   отступов и без прокрутки широких SVG.

   Классы вида *-form (alert-rules-form, member-remove-form, key-create-form
   и т.д.) сюда сознательно НЕ добавлены: это семантические маркеры форм для
   чтения разметки, а не стилевые хуки, и пустые правила под них были бы
   мусором. */

/* Ширину не ограничиваем — см. комментарий у .issue-detail: потолок задаёт
   контентная колонка. */
.endpoint-detail,
.trace-detail {
	display: grid;
	gap: 12px;
	min-width: 0;
}

/* Обёртки широких SVG: график шире узкого экрана, и обрезать его нельзя.
   Все, кроме .trace-flame, стоят в паре с .card — фон, рамка, радиус и
   внутренний отступ приходят оттуда, поэтому здесь только прокрутка. */
.endpoint-chart,
.metric-chart-wrap,
.flamegraph-wrap,
.trace-waterfall,
.trace-flame {
	overflow-x: auto;
}
/* .trace-flame — единственная из них без .card: отступ задаём сами. */
.trace-flame {
	padding: 12px;
}

/* .web-vitals-panel — это <table class="data-table web-vitals-panel">, а не
   блок-панель. Здесь был display:grid, из-за чего thead и tbody становились
   ячейками сетки и таблицу «рвало»: шапка уезжала влево, тело вправо.
   Раскладку задаёт .data-table, здесь только отступ. */
.web-vitals-panel {
	margin-bottom: 12px;
}

.trace-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	align-items: center;
}

/* Разбор проблемы, полный запрос и привязка к коду — секции над сухими
   числами evidence: сначала «что не так», потом сам запрос, потом где в коде. */
.perf-explain, .perf-query-sec, .perf-code-sec {
	display: grid;
	gap: 8px;
	margin-bottom: 12px;
}
.perf-explain p { margin: 0; }
.perf-explain .perf-fix { margin-top: 8px; color: var(--text-dim); }
.perf-explain .perf-fix strong { color: var(--text); font-weight: 600; }
.perf-query-meta {
	margin-left: 8px;
	font-size: var(--fs-sm);
	font-weight: 400;
	color: var(--text-dim);
	font-variant-numeric: tabular-nums;
}
/* Полный текст запроса: НЕ обрезаем — переносим длинные строки, оставляя
   горизонтальную прокрутку на крайний случай (одна очень длинная лексема). */
.perf-query {
	margin: 0;
	background: var(--surface-2);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 12px 14px;
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
	line-height: 1.5;
	color: var(--text);
	white-space: pre-wrap;
	word-break: break-word;
	overflow-x: auto;
}
.perf-code {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px 10px;
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
}
.perf-code-loc { color: var(--text); }
.perf-code-fn { color: var(--text-dim); }

.perf-evidence {
	display: grid;
	gap: 8px;
	margin-bottom: 12px;
}
/* .perf-evidence-list — это <dl> с парами dt/dd, а не маркированный список:
   без сетки браузер отбивает <dd> отступом и пары уезжают лесенкой. */
.perf-evidence-list {
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: 4px 16px;
	margin: 0;
	align-items: baseline;
}
.perf-evidence-list dt {
	color: var(--text-dim);
}
.perf-evidence-list dd {
	margin: 0;
	font-variant-numeric: tabular-nums;
}
/* URL — машинная строка: моноширинная и переносится по любому символу, иначе
   длинный путь распирает карточку по горизонтали. */
.perf-urls {
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
	overflow-wrap: anywhere;
}
/* .perf-sample-trace — абзац-обёртка вокруг кнопки «пример трейса». Моно
   здесь ставить нельзя: шрифт наследовала бы сама кнопка (и наследовала —
   правка после визуального обхода). */
.perf-sample-trace {
	margin: 12px 0 0;
}

/* Публичная status-страница: фон задаётся на <body>, потому что у неё свой
   layout без app-shell. */
.status-body {
	padding: 24px 16px;
}
.status-incidents,
.status-maintenance {
	margin-bottom: 16px;
}

.inline {
	display: inline;
}

/* .visually-hidden — текст только для скринридера. Нужен там, где состояние
   несёт иконка: @icon печатает aria-hidden, и без дублирующего текста
   «сделано»/«не сделано» пункты чек-листа звучат одинаково. */
.visually-hidden {
	position: absolute;
	/* Приколот к левому краю containing block: без left абсолютный спан
	   остаётся в своей статической позиции — внутри широкой таблицы это
	   точка за правым краем вьюпорта, и невидимый однопиксельный элемент
	   расширял scrollWidth документа (горизонтальный скролл на 375px).
	   Порядок чтения скринридером задаёт DOM, а не позиция — ему всё равно. */
	left: 0;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Disclosure «Что это за раздел?»: стрелка — иконка спрайта (раньше здесь
   стоял текстовый глиф ▸/▾ через ::before, он был другой толщины и не
   следовал иконографике). Поворот на раскрытии — чистый CSS, без JS. */
.help-panel summary .ic {
	color: var(--text-dim);
	transition: transform 120ms ease;
}
.help-panel[open] summary .ic {
	transform: rotate(90deg);
}

/* Кнопка «+ …»: иконка и подпись на одной линии. */
.create-trigger {
	gap: 6px;
}

/* Поля под небольшие числа (throttle, порог, окно) не растягиваем на всю
   карточку: поле шириной 500px под двузначное число выглядит как ошибка
   вёрстки и сбивает ожидание от вводимого значения. */
.input[type="number"] {
	max-width: 160px;
}

/* Таблицы, живущие внутри карточки во всю ширину, тянем на всю её ширину:
   иначе получается широкая карточка с узкой таблицей у левого края и
   большим пустым полем справа. */
.card .data-table,
.panel .data-table,
.scroll-list .data-table,
.issue-events {
	width: 100%;
}
/* Ширину таблицы тегов задаёт модификатор .data-table--auto в разметке
   (см. issuedetail.templ): она вне карточки, поэтому селектор с .card её не
   доставал, а общее умолчание width:100% растягивало список ключ/значение
   через всю страницу. */

/* ===== Правки после визуального обхода 2026-07-20 ===== */

/* min-width:0 нужен, чтобы карточка-грид-элемент могла быть уже своего
   содержимого; без него длинные машинные строки распирают колонку сетки. */
.card,
.panel {
	min-width: 0;
}

/* Машинные строки внутри карточки переносятся, а не уезжают за край. Раньше
   DSN обрывался на полуслове, а кнопка «Отозвать» в таблице ключей была
   срезана правой границей. Горизонтальная прокрутка тут не годится: она
   прячет содержимое ровно так же, просто с полосой. */
.card pre,
.panel pre {
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}
/* break-word, а не anywhere: anywhere рвёт СЛОВО в любом месте, даже когда
   строка уместилась бы переносом по границам. На узкой колонке (~70px это
   давало «alice / @ac / me.e / xamp / le» вместо «alice@ / acme.example» —
   данные превращались в вертикальный столбик букв. break-word рвёт длинное
   слово только тогда, когда иначе оно не влезает вовсе, что и требовалось:
   правило вводилось против распирания карточки, а не против переноса. */
.card .data-table td,
.panel .data-table td {
	overflow-wrap: break-word;
	word-break: normal;
}
/* Числовые колонки не переносим: разрыв числа посреди разряда нечитаем. */
.card .data-table td.num,
.panel .data-table td.num {
	overflow-wrap: normal;
	white-space: nowrap;
}
/* Подписи кнопок и бейджей тоже не рвём: «Отозвать» переносилось как
   «Отозва/ть». Перенос по любому символу нужен только машинным строкам
   (ключам, DSN, URL), а не элементам управления. */
.card .data-table td .btn,
.card .data-table td .badge,
.panel .data-table td .btn,
.panel .data-table td .badge {
	overflow-wrap: normal;
	white-space: nowrap;
}

/* Соседние чекбоксы не слипаются: у .checkbox-inline свой внутренний gap, но
   между двумя такими блоками зазора не было — получалось «eu-west☑local». */
.checkbox-inline {
	margin: 2px 14px 2px 0;
}

/* Чекбокс с подсказкой под ним. Своя обёртка, а не .field: там `label`
   раскладывается колонкой (подпись НАД полем — верно для текстовых полей), и
   чекбокс в такой обёртке уезжал над собственным текстом, набирая заодно
   приглушённый цвет и мелкий кегль подписи. Подсказка выравнивается по тексту,
   а не по контролу: 18px квадрат + 8px gap внутри .checkbox-inline. */
.checkbox-field {
	margin-bottom: 12px;
}
.checkbox-field .hint {
	display: block;
	margin: 4px 0 0 26px;
}

/* Сортируемые заголовки таблиц: ссылка в <th> наследует цвет заголовка, а не
   акцент. Синие заголовки колонок спорили с данными и читались как обычные
   ссылки. */
.data-table th a {
	color: inherit;
	text-decoration: none;
}
.data-table th a:hover {
	color: var(--text);
	text-decoration: underline;
}

/* Полоска доступности внутри карточки занимает её целиком (упиралась в
   собственную max-width и оставляла полкарточки пустой). Стек задержек теперь
   график с осями — у него своя, более узкая ширина (.monitor-chart выше). */
.card > .availability-bars {
	width: 100%;
	max-width: none;
}

/* Бейдж внутри таблицы чуть у́же по горизонтали, чтобы три бейджа в строке
   (web-vitals) не превращали её в гирлянду. Кегль общий со всеми бейджами —
   отдельно ужимать его больше не нужно. */
.data-table .badge {
	padding: 2px 6px;
}

/* Стат-плитка с текстовым значением (например «39 дн. до истечения»): крупный
   кегль числа для фразы не годится — рядом с «100.00%» она выглядела
   инородно. */
.stat-tile--text .stat-value {
	font-size: 14px;
	font-weight: 600;
	line-height: 1.35;
}

/* Fieldset формы монитора выглядит как карточка дизайн-системы: собственная
   рамка с «вырезом» под legend — единственное место в продукте, где группа
   полей оформлена иначе, чем везде. Семантику fieldset/legend сохраняем: она
   нужна скринридеру для группировки полей. */
.monitor-form fieldset {
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	background: var(--surface);
	padding: 14px 16px;
	margin: 0 0 16px;
	min-width: 0;
}
.monitor-form legend {
	padding: 0 6px;
	color: var(--text-dim);
	font-size: var(--fs-xs);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .4px;
}
/* Группы чекбоксов (регионы, каналы) — сетка, а не строка вплотную. */
.monitor-form fieldset > .checkbox-inline {
	margin-right: 18px;
}

/* Пояснение к Apdex над таблицей — служебная сноска, а не заголовок: без
   стиля она читалась как отладочная строка. */
.apdex-note,
.perf-cap-note {
	margin: 0 0 8px;
	color: var(--text-dim);
	font-size: var(--fs-sm);
}

/* Формы порогов (регрессии, производительность) раскладываются в несколько
   колонок: десять полей в один столбец растягивали карточку на всю высоту
   экрана, оставляя соседние колонки сетки пустыми. Кнопка и чекбокс
   «Включено» занимают всю ширину последней строкой. */
.project-regression-form,
.project-perf-form {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
	gap: 8px 12px;
	align-items: end;
}
.project-regression-form > .checkbox-inline,
.project-regression-form > button,
.project-perf-form > button {
	grid-column: 1 / -1;
	justify-self: start;
}

/* Подсветка столбика графика частоты при наведении: нативная подсказка
   <title> всплывает с задержкой, и без визуального отклика непонятно, на
   каком именно столбике стоит курсор. Оформленной подсказки нет сознательно —
   она потребовала бы JS, а график должен работать и без него. */
.chart-freq rect {
	transition: opacity 100ms ease;
}
.chart-freq:hover rect {
	opacity: .45;
}
.chart-freq rect:hover {
	opacity: 1;
}

/* ===== Человек в интерфейсе: цветной аватар + чип ===== */

/* Цвет аватара выводится из хеша адреса (см. avatarColorClass в
   userchip.templ) и задаётся классом, а не inline-style: CSP запрещает
   style="". Хранится только тон, светлота подбирается под тему. */
.avatar--c0 { --avatar-h: 210; }
.avatar--c1 { --avatar-h: 265; }
.avatar--c2 { --avatar-h: 330; }
.avatar--c3 { --avatar-h: 10; }
.avatar--c4 { --avatar-h: 35; }
.avatar--c5 { --avatar-h: 145; }
.avatar--c6 { --avatar-h: 175; }
.avatar--c7 { --avatar-h: 80; }

.avatar[class*="avatar--c"] {
	background: hsl(var(--avatar-h) 45% 24%);
	color: hsl(var(--avatar-h) 75% 78%);
}
:root[data-theme="light"] .avatar[class*="avatar--c"] {
	background: hsl(var(--avatar-h) 72% 90%);
	color: hsl(var(--avatar-h) 55% 30%);
}
@media (prefers-color-scheme: light) {
	:root:not([data-theme="dark"]) .avatar[class*="avatar--c"] {
		background: hsl(var(--avatar-h) 72% 90%);
		color: hsl(var(--avatar-h) 55% 30%);
	}
}

/* Размер для таблиц и шапки: базовый .avatar рассчитан на рейл. */
.avatar--sm {
	width: 22px;
	height: 22px;
	font-size: var(--fs-label);
	margin-top: 0;
	flex: none;
}

.user-chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-width: 0;
	vertical-align: middle;
}
.user-chip-name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
/* «не назначено» — приглушённо: это отсутствие состояния, а не значение. */
.user-chip--empty {
	color: var(--text-mute);
	font-style: italic;
}

/* Смена ответственного свёрнута: в шапке показан факт назначения, здесь —
   только действие. */
/* Приглушённый цвет здесь читался как «кнопка выключена»: контрол доступен
   всегда, а выглядел неактивным. Обычный цвет текста плюс явный маркер
   раскрытия — теперь видно, что это раскрывающийся блок, а не disabled. */
.assign-control > summary {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	list-style: none;
	cursor: pointer;
	padding: 6px 10px;
	border: 1px solid var(--border-control);
	border-radius: var(--radius);
	color: var(--text);
	font-weight: 500;
}
.assign-control > summary::after {
	content: "";
	width: 0;
	height: 0;
	margin-left: 2px;
	border-left: 4px solid transparent;
	border-right: 4px solid transparent;
	border-top: 5px solid currentColor;
	transition: transform 0.15s ease;
}
.assign-control[open] > summary::after {
	transform: rotate(180deg);
}
.assign-control > summary::-webkit-details-marker {
	display: none;
}
.assign-control > summary:hover {
	border-color: var(--text-dim);
	background: var(--surface-2);
}
.assign-control .assign-form {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-top: 8px;
	/* См. .team-member-add-form: кнопка «Назначить» уезжала за край на 375px. */
	flex-wrap: wrap;
}
.assign-control .assign-form .select {
	width: auto;
	min-width: 200px;
	/* min-width в 200px шире, чем колонка на телефоне: без max-width селект
	   сам распирает форму и возвращает ту же горизонтальную прокрутку. */
	max-width: 100%;
}

/* Ответственный — отдельной строкой под бейджами уровня и статуса: в одной
   строке с ними он читался как ещё один бейдж, а это другого рода факт. */
.issue-assignee {
	margin: 8px 0 0;
}

/* ===== Оси, легенда и подсказки графиков (доработка 2026-07-20) ===== */

/* Графики на детали эндпойнта занимают карточку целиком — как график частоты
   на детали issue. Собственный max-width у .chart/.chart-freq рассчитан на
   узкую врезку в списке. */
.endpoint-chart .chart,
.endpoint-chart .chart-freq,
.endpoint-chart .latency-chart {
	max-width: none;
	width: 100%;
	height: auto;
}

/* Полоса наведения над интервалом линейного графика: прозрачная, потому что
   наводиться на саму линию нечем — она в полтора пикселя. При наведении
   подсвечивается, чтобы было видно, к какому интервалу относится подсказка. */
.hover-band {
	fill: transparent;
}
.hover-band:hover {
	fill: var(--accent-soft);
}

/* Подпись под заголовком графика: что измеряется и с каким шагом агрегации. */
.chart-caption {
	margin: -4px 0 6px;
	color: var(--text-dim);
	font-size: var(--fs-sm);
}

/* Легенда перцентилей: цвет линии сам по себе ничего не сообщает — две линии
   разного цвета без подписи неразличимы по смыслу. */
.chart-legend {
	display: flex;
	gap: 16px;
	margin: 0 0 8px;
	font-size: var(--fs-sm);
	color: var(--text-dim);
}
.legend-item {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}
.legend-item::before {
	content: "";
	width: 14px;
	height: 2px;
	border-radius: 1px;
	background: currentColor;
}
.legend-p50::before { background: var(--accent); }
.legend-p95::before { background: #7c5cff; }

/* Свотчи фаз задержки (DNS/TCP/TLS/TTFB) = цвета сегментов столбика
   (.latency-chart .seg-*). Стоят ПОСЛЕ .legend-item::before, иначе его
   currentColor перебивал бы цвет (одинаковая специфичность → побеждает
   поздний — из-за этого свотчи были серыми). Фаза — заливка, а не линия,
   поэтому свотч квадратный, а не 2px-штрих. */
.legend-dns::before  { background: var(--phase-dns); }
.legend-tcp::before  { background: var(--phase-tcp); }
.legend-tls::before  { background: var(--phase-tls); }
.legend-ttfb::before { background: var(--phase-ttfb); }
.legend-dns::before,
.legend-tcp::before,
.legend-tls::before,
.legend-ttfb::before { width: 11px; height: 11px; border-radius: 3px; }

/* ===== Форма окна обслуживания ===== */

/* Тип окна взаимоисключающий, поэтому в форме виден только выбранный блок.
   Скрытие — чистый CSS через :has() по radio, без JS: галка «Разовое»
   прячет еженедельный блок и наоборот. Скрытые поля всё равно уходят в
   POST, но хендлер читает только выбранную ветку, так что это безопасно. */
.maintenance-form:has(input[name="kind"][value="oneoff"]:checked) .mw-weekly,
.maintenance-form:has(input[name="kind"][value="weekly"]:checked) .mw-oneoff {
	display: none;
}

/* Поле «Свой IANA часовой пояс» видно только при выборе «Другой» (пустая
   option в select). Пока выбран любой готовый пояс, поле не нужно и только
   загромождает форму. :has по выбранной option, без JS. */
.maintenance-form:not(:has(select[name="timezone"] option[value=""]:checked)) .mw-custom-tz {
	display: none;
}

/* Fieldset здесь — только семантика группировки radio «Тип окна» (№79): поле
   выглядит как остальные .field, поэтому дефолтные рамка/отступы браузера
   снимаются (семантику сохранять — тот же принцип, что у .monitor-form
   fieldset). */
.maintenance-form fieldset.field {
	border: 0;
	padding: 0;
	margin: 0;
	min-width: 0;
}
.maintenance-form legend.field-label {
	padding: 0;
}

/* Сегмент-контрол «Разовое / Еженедельное» — две склеенные кнопки, как у
   переключателя языка/темы: сразу читается как выбор одного из двух, а не
   как флажок-опция. Радио скрыты, подсвечивается активная подпись. */
.segmented {
	display: inline-flex;
	border: 1px solid var(--border-control);
	border-radius: var(--radius);
	overflow: hidden;
	width: fit-content;
}
.segmented label {
	display: inline-flex;
	align-items: center;
	padding: 6px 14px;
	cursor: pointer;
	color: var(--text-dim);
	font-size: var(--fs-sm);
	border-left: 1px solid var(--border-control);
}
.segmented label:first-child {
	border-left: none;
}
.segmented input {
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}
.segmented label:has(input:checked) {
	background: var(--accent-soft);
	/* --link, не --accent: тот рассчитан как ФОН под белой подписью и на
	   --accent-soft давал 2.98:1 (тёмная) / 3.95:1 (светлая) — см. тот же
	   выбор у .lang-btn.is-active. Полупрозрачная граница цвета ссылки —
	   второй признак выбора помимо заливки: у самой заливки к соседям всего
	   1.17:1. Дословно тот же приём, что у переключателей языка/темы. */
	color: var(--link);
	border-color: color-mix(in srgb, var(--link) 45%, transparent);
}
.segmented label:has(input:focus-visible) {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}
/* Подпись сегмента набирается как обычная подпись поля. */
.field > .field-label {
	display: block;
	margin-bottom: 4px;
	color: var(--text-dim);
	font-size: var(--fs-sm);
}

/* Нижний ряд (часовой пояс + свой IANA): подписи разной длины, поэтому
   прижимаем контролы к низу ячейки — иначе поле с однострочной подписью
   стоит выше поля с двухстрочной. */
.maintenance-form .form-grid .field label {
	height: 100%;
}
.maintenance-form .form-grid .field .input,
.maintenance-form .form-grid .field .select {
	margin-top: auto;
}

/* Списки участников и проектов в карточке команды — фиксированной высоты со
   внутренним скроллом, чтобы соседние карточки не отличались по высоте из-за
   разного числа строк. Высота ~ три строки плюс заголовок столбца. */
.team-scroll {
	height: 240px;
	overflow-y: auto;
	margin-bottom: 4px;
}
/* Заголовок столбца остаётся на месте при прокрутке. */
.team-scroll .data-table thead th {
	position: sticky;
	top: 0;
	background: var(--surface);
	z-index: 1;
}

/* Чекбоксы и радио рисуем сами. Нативный контрол берёт системную рамку,
   которая на обеих темах даёт 1.06-1.09:1 — втрое ниже порога WCAG 1.4.11 для
   границы интерактивного элемента, и пустой чекбокс на карточке практически
   не виден. Дорисовывать её снаружи (как было: outline поверх нативного
   квадрата) — тупик: браузер рисует свою заливку с отступом от края элемента,
   поэтому линия ложилась не по краю, а вокруг него с зазором, и читалась как
   лишняя рамка. Граница должна БЫТЬ краем контрола, а для этого контрол
   должен быть нашим.
   appearance: none снимает системную отрисовку целиком; дальше квадрат,
   граница и галочка — обычный CSS, одинаковый в обеих темах. */
input[type="checkbox"],
input[type="radio"] {
	appearance: none;
	-webkit-appearance: none;
	/* Браузерный размер по умолчанию — 13×13: цель нажатия вдвое меньше
	   минимума WCAG 2.5.8, и на телефоне в таблице по ней промахиваются. */
	width: 18px;
	height: 18px;
	/* Добираем до 24×24 полем вокруг: увеличивать сам квадрат сильнее значит
	   ломать плотность таблицы. Внутри .checkbox-inline поле снимается —
	   там цель нажатия это вся подпись. */
	margin: 3px;
	flex: none;
	border: 1.5px solid var(--border-control);
	border-radius: 4px;
	background: var(--surface);
	display: inline-grid;
	place-content: center;
	cursor: pointer;
}
input[type="radio"] {
	border-radius: 50%;
}
/* Галочка и точка — фигуры внутри контрола, а не глиф шрифта: clip-path
   рисует их в любом окружении одинаково, тогда как «✓» зависит от шрифта
   системы. Через scale(0)/scale(1), чтобы переход состояния не дёргал
   раскладку. */
input[type="checkbox"]::before,
input[type="radio"]::before {
	content: "";
	background: var(--accent-fg);
	transform: scale(0);
}
input[type="checkbox"]::before {
	width: 10px;
	height: 10px;
	clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0, 43% 62%);
}
input[type="radio"]::before {
	width: 8px;
	height: 8px;
	border-radius: 50%;
}
input[type="checkbox"]:checked,
input[type="radio"]:checked {
	background: var(--accent);
	border-color: var(--accent);
}
input[type="checkbox"]:checked::before,
input[type="radio"]:checked::before {
	transform: scale(1);
}
input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
	cursor: not-allowed;
	opacity: .55;
}
/* Фокус-ринг обязан перебивать рамку покоя. Правило выше имеет ту же
   специфичность, что общий input:focus, но стоит ПОЗЖЕ — и выигрывало даже в
   фокусе, из-за чего у чекбоксов и радио фокус переставал быть виден вовсе
   (WCAG 2.4.7). Здесь состояние :focus поднимает специфичность до (0,2,1). */
input[type="checkbox"]:focus,
input[type="radio"]:focus,
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 1px;
}

/* Пользователь просил уменьшить анимацию — уважаем. Переходов в продукте всего
   три (поворот шеврона, смена прозрачности), поэтому цена правки нулевая, но
   заявленное системой предпочтение должно работать, а не игнорироваться. */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

/* Предупреждение о схлопнутой кардинальности. Не .error и не .warning: это не
   поломка и не ошибка оператора, а диагноз с инструкцией — оформляем как
   заметный, но спокойный блок. */
.cardinality-notice {
	background: color-mix(in srgb, var(--warn) 10%, transparent);
	border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
	border-radius: var(--radius-lg);
	padding: 12px 14px;
	margin: 0 0 16px;
}
.cardinality-notice p {
	margin: 0 0 6px;
}
.cardinality-notice p:last-child {
	margin-bottom: 0;
}
.cardinality-notice-head {
	font-weight: 650;
	color: var(--warn-ink);
}
/* Примеры — главная ценность блока, поэтому моноширинные и с воздухом:
   именно по ним человек узнаёт идентификатор внутри имени. */
.cardinality-samples {
	margin: 4px 0 8px;
	padding-left: 18px;
	list-style: disc;
}
.cardinality-samples code {
	font-size: var(--fs-sm);
	overflow-wrap: anywhere;
}

/* Сообщение о результате действия. Показывается один раз после редиректа,
   поэтому не занимает место постоянно и не требует кнопки закрытия. */
.flash {
	position: relative;
	background: color-mix(in srgb, var(--good) 12%, transparent);
	border: 1px solid color-mix(in srgb, var(--good) 45%, transparent);
	color: var(--good-ink);
	border-radius: var(--radius);
	padding: 8px 34px 8px 12px;
	margin: 0 0 14px;
	font-size: var(--fs-base);
}
/* Автозакрытие — прогрессивное улучшение под гейтом .js (№124): без JS не
   видна и кнопка закрытия (.flash-close включает скрипт), и безусловная
   анимация уносила сообщение НАВСЕГДА — потерять текст ошибки хуже, чем
   оставить плашку висеть до следующего перехода. С JS держим 6 секунд
   (успеть прочитать даже длинную фразу), затем плавно убираем и схлопываем
   по высоте, чтобы содержимое страницы не дёргалось рывком. */
.js .flash {
	animation: flash-dismiss 0.4s ease-in 6s forwards;
}
/* Таймер останавливается, пока указатель над плашкой или фокус внутри неё:
   6 секунд на чтение хватает не всем, а WCAG 2.2.1 требует способа продлить
   ограничение по времени. Отвести мышь или увести фокус — способ, доступный и
   с клавиатуры. */
.flash:hover,
.flash:focus-within {
	animation-play-state: paused;
}
@keyframes flash-dismiss {
	to {
		opacity: 0;
		/* visibility обязательна: opacity:0 убирает плашку с ГЛАЗ, но
		   оставляет её в дереве доступности и в порядке табуляции — Tab
		   попадал в невидимую кнопку закрытия. */
		visibility: hidden;
		margin-bottom: 0;
		padding-top: 0;
		padding-bottom: 0;
		max-height: 0;
		border-width: 0;
		overflow: hidden;
	}
}
/* Пользователь просил уменьшить анимацию — не убираем сообщение вовсе, а
   прячем его сразу без движения: анимация тут функциональна, а не декоративна,
   и полностью отменять её значило бы оставить плашку висеть навсегда. */
@media (prefers-reduced-motion: reduce) {
	.js .flash {
		animation: flash-dismiss 1ms linear 6s forwards;
	}
}
/* Крестик закрывает сразу. Кнопка, а не ссылка: это действие на странице, а не
   переход, и href="#" сломал бы :target-модалки на той же странице. Без JS
   кнопка просто ничего не делает — сообщение уйдёт само по таймеру, поэтому
   она скрыта до тех пор, пока скрипт её не включит. */
.flash-close {
	display: none;
	position: absolute;
	top: 4px;
	right: 6px;
	background: none;
	border: none;
	color: inherit;
	font-size: 18px;
	line-height: 1;
	padding: 2px 6px;
	cursor: pointer;
}
.js .flash-close {
	display: block;
}
.flash-close:hover {
	opacity: 0.7;
}
/* «Ничего не выбрано» и подобное — не успех, но и не ошибка. */
.flash--warn {
	background: color-mix(in srgb, var(--warn) 12%, transparent);
	border-color: color-mix(in srgb, var(--warn) 45%, transparent);
	color: var(--warn-ink);
}
