/* Qbidr — дизайн-токены. Светлая тема по умолчанию, тёмная — по системной.
   Акцент и градиент взяты с qbidr.com, чтобы панель и сайт читались
   как один продукт. */
:root {
	--bg: #f5f6fa;
	--panel: #ffffff;
	--panel-2: #f8f9fc;
	--ink: #171d2b;
	--muted: #6b7385;
	--line: #e8ebf2;
	--line-strong: #d5d9e3;
	/* --hint — подписи под полями. Отдельно от --muted: подсказка стоит
	   ступенью ниже подписи поля и не должна с ней спорить за внимание. */
	--hint: #a2a5aa;
	--accent: #6d5cff;
	--accent-soft: rgba(109, 92, 255, .10);
	--accent-ink: #ffffff;
	--violet: #8b5cff;
	--cyan: #2dd9ff;
	--grad: linear-gradient(120deg, #8b5cff 0%, #5c8bff 48%, #2dd9ff 100%);
	--ok: #148a51;
	--ok-soft: #e1f4ea;
	--warn: #a96d13;
	--warn-soft: #fdf1dc;
	--bad: #c23a2e;
	--bad-soft: #fbe8e5;
	--radius: 12px;
	--radius-s: 7px;
	--shadow: 0 1px 2px rgba(23, 29, 43, .05), 0 4px 16px rgba(23, 29, 43, .05);
	--shadow-pop: 0 10px 34px rgba(23, 29, 43, .16);
	--side-bg: #05060f;
	--side-ink: #98a0c2;
	--side-hi: #ffffff;
	--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	/* Space Grotesk не грузим со стороны: панель обязана открываться за
	   IP-allowlist без внешней сети. У кого шрифт стоит — увидит фирменный. */
	--display: "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
}
/* Тёмная палитра: применяется по системной теме, если пользователь не выбрал
   светлую явно, и всегда — если выбрал тёмную (настройки панели). */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		--bg: #10141d;
		--panel: #181e2b;
		--panel-2: #1d2432;
		--ink: #e6e9f0;
		--muted: #8d96a8;
		--line: #2a3246;
		--line-strong: #333c50;
		/* На тёмном фоне подсказка светлеет, а не гаснет: тот же #a2a5aa
		   читался бы как основной текст, а более тёмный — не читался бы вовсе. */
		--hint: #7e8697;
		--accent: #8b5cff;
		--accent-soft: rgba(139, 92, 255, .16);
		--ok: #3dbb7d;
		--ok-soft: rgba(61, 187, 125, .14);
		--warn: #d99b3a;
		--warn-soft: rgba(217, 155, 58, .14);
		--bad: #e26055;
		--bad-soft: rgba(226, 96, 85, .14);
		--shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 6px 20px rgba(0, 0, 0, .25);
		--shadow-pop: 0 12px 38px rgba(0, 0, 0, .5);
		--side-bg: #05060f;
		--side-ink: #98a0c2;
	}
}
:root[data-theme="dark"] {
	--bg: #10141d;
	--panel: #181e2b;
	--panel-2: #1d2432;
	--ink: #e6e9f0;
	--muted: #8d96a8;
	--line: #2a3246;
	--line-strong: #333c50;
	/* На тёмном фоне подсказка светлеет, а не гаснет: тот же #a2a5aa
	   читался бы как основной текст, а более тёмный — не читался бы вовсе. */
	--hint: #7e8697;
	--accent: #8b5cff;
	--accent-soft: rgba(139, 92, 255, .16);
	--ok: #3dbb7d;
	--ok-soft: rgba(61, 187, 125, .14);
	--warn: #d99b3a;
	--warn-soft: rgba(217, 155, 58, .14);
	--bad: #e26055;
	--bad-soft: rgba(226, 96, 85, .14);
	--shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 6px 20px rgba(0, 0, 0, .25);
	--shadow-pop: 0 12px 38px rgba(0, 0, 0, .5);
	--side-bg: #0c101a;
	--side-ink: #98a1b5;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
	font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
	background: var(--bg);
	color: var(--ink);
	min-height: 100vh;
	-webkit-font-smoothing: antialiased;
}
/* Каркас приложения живёт внутри #app: сайдбар и содержимое — его дети.
   На странице входа корня приложения нет, там раскладку держит сам main. */
#app { display: flex; min-height: 100vh; }

/* --- Навигация --- */
nav.side {
	width: 212px;
	flex: 0 0 212px;
	background: var(--side-bg);
	color: var(--side-ink);
	display: flex;
	flex-direction: column;
	padding: 16px 10px;
	position: sticky;
	top: 0;
	/* align-self обязателен. По умолчанию флекс растягивает элемент на всю
	   высоту контейнера, height: 100vh при этом игнорируется, и «липкому»
	   элементу некуда прилипать — он просто уезжает вверх вместе со
	   страницей. Видно это только на длинных страницах вроде настроек
	   кампании, поэтому и не бросалось в глаза. */
	align-self: flex-start;
	height: 100vh;
	/* Пунктов больше десятка: на низком экране меню должно прокручиваться
	   само, а не обрезаться. */
	overflow-y: auto;
	overscroll-behavior: contain;
	gap: 2px;
}
nav.side { transition: width .16s ease, flex-basis .16s ease; }
.side-top { display: flex; align-items: center; gap: 6px; padding: 2px 4px 14px; }
nav.side .brand {
	display: flex;
	align-items: center;
	gap: 9px;
	flex: 1 1 auto;
	min-width: 0;
	font-family: var(--display);
	font-weight: 700;
	letter-spacing: -.01em;
	color: var(--side-hi);
	padding: 4px 6px;
	font-size: 19px;
	border-radius: var(--radius-s);
}
/* Знак — не пункт меню: подсветка фона под ним читалась как «сейчас выбрано». */
nav.side a.brand:hover { background: none; color: var(--side-hi); }
/* Размер в em, а не в пикселях: знак должен быть ростом с букву «Q» рядом,
   чуть выше её — и оставаться таким при любом кегле, от сайдбара до входа.
   Видимая часть знака занимает 25 из 32 единиц viewBox (окружность r=11 плюс
   обводка), то есть 0,78 бокса; при 1,05em это примерно 0,82 кегля — как раз
   немногим больше прописной буквы. */
.brand-mark {
	width: 1.05em; height: 1.05em; flex: none;
	filter: drop-shadow(0 0 9px rgba(139, 92, 255, .45));
}
.side-toggle {
	flex: none;
	width: 26px; height: 26px;
	display: grid; place-items: center;
	background: none; border: none; cursor: pointer;
	color: var(--side-ink); opacity: .7; border-radius: 7px;
	transition: background .12s, opacity .12s, transform .16s ease;
}
.side-toggle svg { width: 16px; height: 16px; }
.side-toggle:hover { background: rgba(255, 255, 255, .09); opacity: 1; color: var(--side-hi); }

/* nav-handle — кнопка возврата меню поверх содержимого, в левом нижнем углу.
   Плавающей, а не в потоке: страница со скрытым меню занимает всю ширину, и
   колонка под кнопку вернула бы часть отданного места. */
.nav-handle {
	position: fixed; left: 14px; bottom: 14px; z-index: 40;
	width: 34px; height: 34px; display: grid; place-items: center;
	background: var(--side-bg); color: #fff; opacity: .85;
	border: 1px solid rgba(255, 255, 255, .12); border-radius: 10px;
	box-shadow: var(--shadow-pop); cursor: pointer;
	transition: opacity .12s, transform .12s;
}
.nav-handle svg { width: 16px; height: 16px; transform: rotate(180deg); }
.nav-handle:hover { opacity: 1; transform: translateY(-1px); }

nav.side .nav-sec {
	font-size: 10.5px;
	text-transform: uppercase;
	letter-spacing: .12em;
	color: #5d6577;
	padding: 14px 12px 5px;
	user-select: none;
}
nav.side a {
	display: flex;
	align-items: center;
	gap: 10px;
	color: inherit;
	text-decoration: none;
	padding: 8px 12px;
	border-radius: var(--radius-s);
	transition: background .12s, color .12s;
}
nav.side a .ic {
	width: 18px;
	text-align: center;
	font-size: 13px;
	opacity: .8;
}
nav.side a:hover { background: rgba(255, 255, 255, .07); color: var(--side-hi); }
nav.side a.on {
	background: linear-gradient(90deg, rgba(139, 92, 255, .30), rgba(45, 217, 255, .08));
	color: var(--side-hi);
	font-weight: 600;
}
nav.side a.logout { margin-top: auto; color: #78829a; }

/* Ширину не ограничиваем: таблица кампаний — двадцать колонок, и обрезать её
   рамкой в 1220 пикселей на широком мониторе значит прятать половину.
   Длинные строки текста держит в узде .hint со своим max-width. */
main { flex: 1; min-width: 0; padding: 22px 24px 56px; }
/* Формы — единственное, что ширину ограничивает. Таблице ограничение
   вредит (двадцать колонок обрезать нечем), а поле ввода на широком
   мониторе растягивается на две тысячи пикселей и перестаёт читаться как
   поле: глаз не связывает подпись слева с курсором справа. */
.form-page { max-width: 1040px; }
main.center { display: flex; align-items: center; justify-content: center; min-height: 100vh; max-width: none; }

h1 { font-size: 21px; margin: 0 0 6px; letter-spacing: -.01em; }
h1 + .hint { margin-top: 0; margin-bottom: 18px; }
h2 { font-size: 13px; margin: 0 0 12px; color: var(--muted); font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }
.hint { color: var(--hint); max-width: 760px; font-size: 11px; line-height: 1.45; }
.hint code, li code { background: var(--panel-2); border: 1px solid var(--line); border-radius: 5px; padding: 1px 6px; font-family: var(--mono); font-size: 12px; }
ol.steps { padding-left: 20px; display: flex; flex-direction: column; gap: 8px; }

.page-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.page-head h1 { margin: 0; }

/* --- Карточки и KPI --- */
/* Карточка с таблицей: отступы даёт сама таблица через ячейки, а не
   контейнер. Иначе к 10 пикселям ячейки прибавлялись 18 карточки, и на
   двадцати колонках это уходило в горизонтальную прокрутку на ровном месте. */
/* clip, а не hidden: оба обрезают углы, но hidden делает карточку
   прокручиваемым блоком, и «липкий» заголовок таблицы внутри начинает
   отсчитывать верх от неё, а не от окна — то есть не липнет вовсе. */
.card.flush { padding: 0; overflow: clip; }
.card.flush > .card-head { padding: 14px 16px 12px; margin-bottom: 0; }
.card.flush > .hint { padding: 0 16px 12px; margin: 0; }
.card.flush .tbl th:first-child,
.card.flush .tbl td:first-child { padding-left: 16px; }
.card.flush .tbl th:last-child,
.card.flush .tbl td:last-child { padding-right: 16px; }
.card.flush .tbl tr:last-child td { border-bottom: none; }

.card {
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: 18px 20px;
	margin-bottom: 16px;
	box-shadow: var(--shadow);
}
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 16px; }
.kpi {
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: 14px 16px;
	box-shadow: var(--shadow);
	position: relative;
	overflow: hidden;
}
.kpi::before {
	content: "";
	position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
	background: linear-gradient(180deg, var(--accent), transparent);
	opacity: .6;
}
.kpi-label { color: var(--muted); font-size: 12px; }
.kpi-value { font-size: 23px; font-weight: 700; margin-top: 3px; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
/* Составные значения (перцентили, «доля (штук)») не влезают в 23px и рвутся
   на две строки — им отдельный размер. */
.kpi-value.kpi-sm { font-size: 17px; }

/* --- Таблицы --- */
table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
th { color: var(--muted); font-weight: 600; font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; }
tbody tr { transition: background .1s; }
tbody tr:hover { background: var(--panel-2); }
tr:last-child td { border-bottom: none; }
td.num, th.num { text-align: right; }
tr.muted { opacity: .55; }

/* --- Кнопки --- */
.btn {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	background: var(--accent);
	color: var(--accent-ink);
	border: none;
	border-radius: var(--radius-s);
	padding: 8px 16px;
	font: inherit;
	font-weight: 600;
	cursor: pointer;
	text-decoration: none;
	transition: filter .12s, transform .06s;
}
.btn:hover { filter: brightness(1.09); }
.btn:active { transform: translateY(1px); }
.btn:disabled, button.small:disabled, button.danger:disabled {
	opacity: .45; cursor: default; filter: none; transform: none;
}
.btn.secondary { background: var(--panel-2); color: var(--ink); border: 1px solid var(--line-strong); }
.btn.full { width: 100%; justify-content: center; }
button { font: inherit; }
button.small {
	padding: 4px 10px;
	border: 1px solid var(--line-strong);
	background: var(--panel);
	color: var(--ink);
	border-radius: 7px;
	cursor: pointer;
	margin-right: 4px;
	transition: border-color .12s, color .12s;
}
button.small:hover { border-color: var(--accent); color: var(--accent); }
:is(.btn, button, input, select):focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Ползунок — исключение: рамка вокруг всей дорожки читается как ошибка
   ввода, а не как фокус. Показываем фокус на бегунке, где он и уместен. */
input[type="range"] { accent-color: var(--accent); }
input[type="range"]:focus-visible { outline: none; }
input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--accent-soft); }
input[type="range"]:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px var(--accent-soft); }

/* --- Бейджи --- */
.badge { display: inline-block; padding: 2.5px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; white-space: nowrap; }
.badge.draft { background: var(--panel-2); color: var(--muted); border: 1px solid var(--line); }
.badge.pending { background: var(--warn-soft); color: var(--warn); }
.badge.approved, .badge.active { background: var(--ok-soft); color: var(--ok); }
.badge.rejected, .badge.archived { background: var(--bad-soft); color: var(--bad); }
.badge.paused { background: var(--panel-2); color: var(--muted); border: 1px solid var(--line); }

/* --- Формы --- */
/* Селектор намеренно не «form label»: подпись над полем — это раскладка
   поля, а не признак формы. Половина экранов панели (SSP, конструктор
   отчётов, ценообразование) собрана без тега <form> — там нечего
   отправлять, — и с прежним селектором подписи в них вставали в строку с
   полем: «Тип трафика [select]» рядом, а более длинное «Биллинговое
   событие» уже переносом. Строчные подписи объявляют себя сами —
   .inline, .chk, .ssp-switch — и стоят ниже по файлу. */
label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted); }
input, select, textarea {
	font: inherit;
	color: var(--ink);
	padding: 8px 11px;
	border: 1px solid var(--line);
	border-radius: var(--radius-s);
	background: var(--panel);
	transition: border-color .12s, box-shadow .12s;
}
/* Системная стрелка у select прижата к самому краю рамки и в тёмной теме
   почти не видна. Рисуем свою — с отступом и цветом из палитры. */
select {
	appearance: none; -webkit-appearance: none;
	padding-right: 32px;
	background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
		linear-gradient(135deg, currentColor 50%, transparent 50%);
	background-position: calc(100% - 17px) calc(50% + 1px), calc(100% - 12px) calc(50% + 1px);
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
	color: var(--ink);
}
select:disabled { opacity: .55; }
input:focus, select:focus, textarea:focus {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px var(--accent-soft);
}
input::placeholder { color: var(--muted); opacity: .65; }

/* --- Свой выпадающий список ---
   Системный <select> рисует ОС: в тёмной теме он остаётся светлым, а
   раскрытый список — вообще отдельный слой, до которого наши стили не
   доходят. Здесь всё своё, включая состояние «под курсором», которое
   ведут и мышь, и клавиши. */
.sel { position: relative; display: inline-block; min-width: 0; }
.sel-btn {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	font: inherit;
	color: var(--ink);
	text-align: left;
	padding: 8px 11px;
	border: 1px solid var(--line);
	border-radius: var(--radius-s);
	background: var(--panel);
	cursor: pointer;
	transition: border-color .12s, box-shadow .12s;
}
.sel-btn:hover:not(:disabled) { border-color: var(--line-strong); }
.sel-btn:disabled { opacity: .55; cursor: default; }
.sel[aria-disabled="true"] .sel-btn { cursor: default; }
.sel-btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.sel-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sel-val.ph { color: var(--muted); }
/* Шеврон уводим в приглушённый цвет и разворачиваем при раскрытии —
   иначе непонятно, открыт список или просто перерисовался экран. */
/* Общий шеврон нарисован «вправо» и в других местах доворачивается CSS.
   Здесь он смотрит вниз, а при раскрытии — вверх. Размер обязателен: без
   него svg тянется на всю кнопку. */
.sel-btn svg {
	flex: 0 0 auto;
	width: 13px;
	height: 13px;
	color: var(--muted);
	transform: rotate(90deg);
	transition: transform .12s;
}
.sel-btn[aria-expanded="true"] svg { transform: rotate(-90deg); }
.sel-pop {
	/* fixed, а не absolute: сворачиваемые блоки формы обрезают содержимое
	   ради скруглений, и absolute-панель срезалась по их границе. Координаты
	   считает useAnchoredPos — он же не даёт панели уехать за край экрана. */
	position: fixed;
	z-index: 60;
	max-width: min(420px, calc(100vw - 16px));
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	box-shadow: var(--shadow-pop);
	padding: 4px;
	display: flex;
	flex-direction: column;
	overflow: hidden;
}
.sel-search { flex: 0 0 auto; width: 100%; margin-bottom: 4px; }
.sel-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.sel-opt {
	padding: 7px 10px;
	border-radius: 5px;
	cursor: pointer;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	font-size: 13px;
}
/* Курсор клавиатуры и курсор мыши — одно состояние: иначе после стрелок
   вниз подсвечено одно, а кликнется другое. */
.sel-opt.cur { background: var(--accent-soft); }
.sel-opt.on { font-weight: 600; }
/* Второстепенная часть подписи: в списке видна, в кнопке — нет. Так у
   таймзоны в кнопке остаётся «UTC+01:00 · Berlin», а полтора десятка
   городов, по которым её ищут, живут в списке. */
.sel-sub { color: var(--hint); }
.sel-opt.on::after { content: " ✓"; color: var(--accent); }
.sel-opt.off { opacity: .45; cursor: default; }
.sel-empty { padding: 8px 10px; color: var(--hint); font-size: 12px; }

/* --- Поле с единицей измерения ---
   Число и его единица — одно значение, и рамка у них общая: «2» и «$» в
   двух разных прямоугольниках читаются как два поля. Единица подписана
   справа, как в банковских формах и у Adform, а не в подписи сверху —
   там она теряется, когда полей в ряд несколько.
   Короткие числа не тянутся на всю колонку: поле шириной с бюджет
   подсказывает, что в него ждут бюджет, а не строку. */
.field {
	display: inline-flex;
	align-items: stretch;
	max-width: 100%;
	border: 1px solid var(--line);
	border-radius: var(--radius-s);
	background: var(--panel);
	overflow: hidden;
	transition: border-color .12s, box-shadow .12s;
}
.field:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.field > input {
	flex: 1 1 auto;
	min-width: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	text-align: right;
}
.field > input:focus { outline: none; box-shadow: none; }
/* Стрелки-крутилки съедают ширину, налезают на единицу и в поле, куда
   вводят сумму, всё равно бесполезны. */
.field > input[type="number"] { -moz-appearance: textfield; }
.field > input[type="number"]::-webkit-outer-spin-button,
.field > input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.field-unit {
	display: flex;
	align-items: center;
	padding: 0 10px;
	border-left: 1px solid var(--line);
	background: var(--panel-2);
	color: var(--muted);
	font-size: 12px;
	white-space: nowrap;
	user-select: none;
}
.field.money { width: 172px; }
.field.count { width: 148px; }
/* «Авто: 3× дневного» — подпись длиннее любого числа. */
.field.auto { width: 216px; }
.field.pct { width: 104px; }
/* Пустое поле показывает «Без лимита» — текст длиннее числа, и обрезать
   его нельзя: он и есть значение. */
.field > input::placeholder { text-align: right; }
.field.disabled { opacity: .55; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; align-items: start; }
.form-grid .span2 { grid-column: span 2; }
fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; background: var(--panel-2); }
legend { color: var(--ink); font-size: 13px; font-weight: 600; padding: 0 8px; }

.curve-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px; margin: 10px 0; }
.curve-grid label { font-size: 11px; align-items: center; }
.curve-grid input { padding: 4px 4px; text-align: center; width: 100%; }
.curve-actions { display: flex; gap: 8px; }
.curve-actions button { padding: 4px 12px; border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink); border-radius: 7px; cursor: pointer; }
.curve-actions button:hover { border-color: var(--accent); color: var(--accent); }

/* --- Мультивыбор (страны) --- */
.ms { position: relative; }
/* Выбранное живёт ВНУТРИ рамки, а не строкой над ней.
   Снаружи оно ломало выравнивание: два поля рядом (ОС и Языки) начинались
   на одной высоте только пока оба пусты, а стоило выбрать язык — строка
   чипов поднимала его поле, и курсоры в соседних полях оказывались на
   разных уровнях. Внутри рамки поле растёт вниз, а его верх не двигается. */
.ms-box {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 5px;
	min-height: 38px;
	padding: 4px 6px;
	border: 1px solid var(--line);
	border-radius: var(--radius-s);
	background: var(--panel);
	cursor: text;
	transition: border-color .12s, box-shadow .12s;
}
.ms-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.ms-chip {
	display: inline-flex; align-items: center; gap: 4px;
	background: var(--accent-soft); color: var(--ink);
	border-radius: 20px; padding: 3px 6px 3px 11px; font-size: 12.5px; white-space: nowrap;
}
.ms-chip b { cursor: pointer; color: var(--muted); font-weight: 400; font-size: 14px; line-height: 1; padding: 0 2px; }
.ms-chip b:hover { color: var(--bad); }
.ms-search {
	flex: 1 1 120px;
	min-width: 120px;
	border: 0;
	padding: 4px 5px;
	background: transparent;
}
.ms-search:focus { outline: none; box-shadow: none; }
.ms-dropdown {
	position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
	margin-top: 4px; max-height: 260px; overflow: auto;
	background: var(--panel); border: 1px solid var(--line);
	border-radius: var(--radius-s); box-shadow: var(--shadow-pop);
}
.ms-opt { padding: 8px 12px; cursor: pointer; display: flex; justify-content: space-between; gap: 10px; }
.ms-opt:hover { background: var(--accent-soft); }
.checks { display: flex; gap: 16px; flex-wrap: wrap; }
.chk { flex-direction: row; align-items: center; gap: 6px; color: var(--ink); }
.chk input { width: auto; }

/* --- Логин --- */
.login-brand {
	display: flex; align-items: center; gap: 9px; justify-content: center;
	font-family: var(--display); font-weight: 700; font-size: 22px;
	letter-spacing: -.01em; margin-bottom: 18px;
}
.login-box {
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: 16px;
	padding: 30px 32px;
	width: 340px;
	box-shadow: var(--shadow-pop);
}
.login-box form { display: flex; flex-direction: column; gap: 12px; }

.error { color: var(--bad); background: var(--bad-soft); border-radius: var(--radius-s); padding: 8px 12px; font-size: 13px; }
.hidden { display: none; }

.endpoint { font-family: var(--mono); font-size: 12px; word-break: break-all; }

/* Заголовок группы карточек. Класс не «section»: так называется
   сворачиваемый блок формы с рамкой и тенью, и заголовок с тем же именем
   рисовался белой таблеткой, прилипшей к карточке под ним. */
h2.group-title { margin: 22px 0 10px; }
.list-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 12px 16px; }
.list-main { display: flex; align-items: center; gap: 12px; min-width: 0; }
.list-main > div { min-width: 0; }
/* Переключатель режима не сжимается: длинная подсказка фида (IP2Proxy с его
   лицензией и памятью) иначе дожимала его до «B…». Текст пусть переносится —
   ему можно, а орган управления обязан выглядеть одинаково у всех строк. */
.list-row > .sel { flex: 0 0 auto; width: 104px; }
.switch { display: inline-flex; flex-direction: row; align-items: center; cursor: pointer; }
.switch input {
	appearance: none; -webkit-appearance: none;
	width: 34px; height: 19px; flex: none; margin: 0;
	background: var(--line-strong); border: none; border-radius: 20px;
	position: relative; cursor: pointer; transition: background .15s;
}
.switch input::after {
	content: ""; position: absolute; top: 2px; left: 2px;
	width: 15px; height: 15px; border-radius: 50%; background: #fff;
	transition: transform .15s; box-shadow: 0 1px 2px rgba(0, 0, 0, .3);
}
.switch input:checked { background: var(--accent); }
.switch input:checked::after { transform: translateX(15px); }
.switch input:disabled { opacity: .5; cursor: default; }
input[type="checkbox"] { accent-color: var(--accent); }

/* --- Сырые логи --- */
.rl-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.rl-json {
	background: #0e1424; color: #ccd5e6;
	border-radius: var(--radius-s); padding: 10px 12px;
	font-family: var(--mono); font-size: 12px; line-height: 1.45;
	max-height: 340px; overflow: auto; white-space: pre-wrap; word-break: break-word;
}
.rl-row:hover { background: var(--accent-soft); }
@media (max-width: 900px) { .rl-cols { grid-template-columns: 1fr; } }
.mod-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; }
.mod-reason { color: var(--bad); font-size: 12px; }
.creative-head { display: flex; justify-content: space-between; align-items: baseline; }
.creative-url { color: var(--muted); font-size: 12px; word-break: break-all; }

/* --- Карточка «Система» --- */
/* --- Состояние процесса --- */
/* Ровно четыре колонки, а не auto-fit. Автоподбор давал каждой группе своё
   число колонок по числу ячеек в ней: четыре сверху, три под ними, шесть
   ниже — значения одного разряда стояли в плитках разной ширины, и глазу
   не за что было зацепиться. Число ячеек в группе кратно четырём, поэтому
   последний ряд всегда полный. */
.sys-grid {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 8px;
	margin-bottom: 4px;
}
@media (max-width: 1100px) { .sys-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .sys-grid { grid-template-columns: minmax(0, 1fr); } }
.sys-group-label { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--accent); font-weight: 700; margin: 18px 0 8px; }
.sys-group-label:first-child { margin-top: 0; }
/* Плитка, а не строка с пунктиром: значений два десятка, и подчёркивание
   под каждым превращало карточку в разлинованный лист. Колонка флексом —
   чтобы соседи по ряду вытягивались до одной высоты и подписи стояли на
   одной линии, сколько бы строк подсказки ни было внутри. */
.sys-item {
	background: var(--panel-2);
	border: 1px solid var(--line);
	border-radius: 10px;
	padding: 9px 12px 10px;
	display: flex;
	flex-direction: column;
	gap: 3px;
}
.sys-label { color: var(--muted); font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; }
.sys-value { font-weight: 600; font-variant-numeric: tabular-nums; }
.sys-value .hint { font-weight: 400; margin-top: 2px; }
/* Несколько бейджей в одной ячейке (домены сертификатов): без обёртки с
   зазором они слипались в одну зелёную полосу. */
.sys-badges { display: flex; flex-wrap: wrap; gap: 5px; }

/* --- Тосты --- */
#toast-root {
	position: fixed; right: 20px; bottom: 20px; z-index: 100;
	display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
}
.toast {
	background: var(--ink); color: var(--bg);
	border-radius: 10px; padding: 9px 16px;
	font-size: 13px; font-weight: 600;
	box-shadow: var(--shadow-pop);
	opacity: 0; transform: translateY(8px);
	transition: opacity .25s, transform .25s;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast.err { background: var(--bad); color: #fff; }

/* --- Переключатель диапазона (страница «Нагрузка») --- */
.range-tabs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.range-tabs button {
	background: transparent; color: var(--muted);
	border: 1px solid var(--line); border-radius: 9px;
	padding: 7px 14px; font-size: 13px; font-weight: 600; cursor: pointer;
	transition: background .15s, color .15s, border-color .15s;
}
.range-tabs button:hover { color: var(--ink); border-color: var(--line-strong); }
.range-tabs button.on {
	background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.range-tabs .hint { margin-left: auto; }

/* --- Раздел «Хранение»: занятость диска и кнопка очистки --- */
button.danger { background: var(--bad); border-color: var(--bad); color: #fff; }
button.danger:hover { filter: brightness(1.08); }
.disk-bar {
	height: 10px; border-radius: 6px; background: var(--panel-2);
	border: 1px solid var(--line); overflow: hidden; margin: 6px 0 4px;
}
.disk-bar > span { display: block; height: 100%; background: var(--accent); }
.disk-bar.warn > span { background: var(--warn); }
.disk-bar.crit > span { background: var(--bad); }
.storage-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.purge-row { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin-top: 18px; }
.purge-row label { display: flex; flex-direction: column; gap: 5px; font-size: 12px; color: var(--muted); }
.purge-row input { width: 110px; }
.purge-row .hint { flex: 1 1 260px; }

/* --- Мобильная адаптация --- */
@media (max-width: 760px) {
	/* Флекс-контейнером стал #app, а не body: правило осталось от серверной
	   вёрстки и после переезда перестало работать — меню разворачивалось в
	   строку на всю ширину, а содержимое уезжало за правый край экрана. */
	#app { flex-direction: column; }
	nav.side {
		width: 100%; flex: none; height: auto; position: static;
		flex-direction: row; flex-wrap: wrap; align-items: center;
		align-self: stretch; overflow-y: visible;
	}
	.side-top { padding: 2px 4px; }
	nav.side .nav-sec { display: none; }
	nav.side a.logout { margin-top: 0; margin-left: auto; }
	main { padding: 16px; }
	.form-grid { grid-template-columns: 1fr; }
	.form-grid .span2 { grid-column: span 1; }
	.curve-grid { grid-template-columns: repeat(6, 1fr); }
}

/* ================================================================
   Интерфейс приложения: панель фильтров, таблицы с выбором колонок,
   вкладки, модальные окна. Всё, что появилось вместе с переходом на
   одностраничную панель.
   ================================================================ */

/* --- Шапка страницы и карточки --- */
.page-head { align-items: flex-start; }
.page-head .hint { margin: 4px 0 0; }
.page-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.card-head h2 { margin: 0; }
.card-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* --- Панель фильтров над таблицей --- */
.toolbar {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
	margin-bottom: 12px;
}
.toolbar-gap { flex: 1 1 auto; }

/* Сетка графиков: четыре распределения задержек рядом, чтобы сравнивать их
   глазами, а не прокруткой. Ниже 760px колонка одна — на телефоне сравнение
   всё равно невозможно, а сжатый до 180px график нечитаем. */
.chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 14px; }
.chart-grid > .card { margin: 0; }

/* Панель масштаба. Ползунок занимает остаток строки: чем он длиннее, тем
   точнее попадание в нужную минуту. */
.zoombar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.zoom-label { color: var(--muted); font-size: 12px; }
.zoom-slider { flex: 1 1 180px; min-width: 120px; accent-color: var(--accent); }
.zoom-range { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }

/* Медленные запросы. Текст SQL по умолчанию в две строки: список из сорока
   развёрнутых запросов — это экран, который никто не читает целиком. */
.chq-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.chq { border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; background: var(--panel-2); }
.chq-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.chq-ms { font-variant-numeric: tabular-nums; }
.chq-ms.warn { color: var(--warn, #dd6b20); }
.chq-kind { color: var(--muted); }
.chq-sql {
	margin: 6px 0 0; white-space: pre-wrap; word-break: break-word; cursor: pointer;
	font-family: var(--mono); font-size: 12px; line-height: 1.45; color: var(--ink);
}
.chq-sql.clip { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* У flush-карточки нет своего padding — иначе таблица не дотянулась бы до
   краёв. Значит, отступы обязаны быть у всего, что стоит над таблицей:
   панели фильтров и пагинатора. Без этого они прилипают к рамке. */
.card.flush > .toolbar { padding: 12px 16px; margin: 0; border-bottom: 1px solid var(--line); }
.toolbar-search { min-width: 240px; flex: 0 1 320px; }
.toolbar label.inline,
label.inline { display: inline-flex; flex-direction: row; align-items: center; gap: 6px; }

/* Полоса массовых действий появляется только при выборе строк, поэтому
   намеренно заметная: она перекрывает обычный ритм страницы и напоминает,
   что следующая кнопка подействует не на одну кампанию. */
.bulkbar {
	display: flex;
	align-items: center;
	gap: 12px;
	flex-wrap: wrap;
	padding: 10px 14px;
	margin-bottom: 12px;
	border-radius: var(--radius-s);
	background: var(--accent-soft);
	border: 1px solid var(--accent);
}
.linkish {
	background: none; border: none; padding: 0;
	color: var(--accent); cursor: pointer; font: inherit; text-decoration: underline;
}

/* --- Таблица --- */
/* Прокрутка вбок появляется только тогда, когда таблица не влезла:
   прокручиваемая обёртка отменяет «липкость» заголовка относительно окна
   (см. app/stickyhead.js). Потолок по высоте — для того же случая: внутри
   обёртки заголовку тоже надо к чему-то липнуть. */
.tbl-wrap { margin: 0 -4px; padding: 0 4px; }
.tbl-wrap.wide { overflow: auto; max-height: calc(100vh - 150px); }
/* Заголовок остаётся на виду при прокрутке. Фон обязателен: под ним
   проезжают строки, а сквозь прозрачную ячейку они видны. Линию снизу
   рисуем тенью, а не рамкой: при border-collapse рамка принадлежит сетке
   таблицы и остаётся на месте, когда ячейка уезжает вверх. */
.tbl > thead > tr > th {
	position: sticky; top: 0; z-index: 2;
	background: var(--panel);
	box-shadow: inset 0 -1px 0 var(--line);
}
/* Вложенная таблица (креативы внутри раскрытой кампании) свой заголовок не
   держит: он встал бы поверх заголовка внешней, и две шапки на одной
   строке читаются как одна сломанная. */
.tbl.sub > thead > tr > th { position: static; box-shadow: none; }
/* Действия строки — второстепенные: акцентная заливка в таблице должна
   быть одна, и это не «Удалить». */
.row-actions { display: flex; gap: 6px; justify-content: flex-end; white-space: nowrap; }
.tbl th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.tbl th.sortable:hover { color: var(--accent); }
.tbl th.tbl-pick, .tbl td.tbl-pick { width: 34px; padding-right: 0; }
.tbl th.tbl-exp, .tbl td.tbl-exp { width: 26px; padding-left: 4px; padding-right: 0; }
.tbl td.tbl-empty { text-align: center; color: var(--muted); padding: 26px 10px; }
.tbl tr.open > td { background: var(--accent-soft); }
.tbl tr.tbl-sub > td { background: var(--panel-2); padding: 12px 16px; }
.tbl tr.tbl-sub:hover { background: var(--panel-2); }
.exp-btn {
	display: grid; place-items: center;
	width: 22px; height: 22px; padding: 0;
	background: none; border: none; cursor: pointer;
	color: var(--muted); border-radius: 6px;
	transition: background .12s, color .12s;
}
.exp-btn svg { width: 13px; height: 13px; transition: transform .15s ease; }
.exp-btn:hover { background: var(--accent-soft); color: var(--accent); }
/* Поворот одного и того же шеврона вместо пары ▸/▾: у тех разные метрики,
   и «вниз» вставал на пиксель ниже «вправо». */
.exp-btn.open svg { transform: rotate(90deg); }
/* Вложенная таблица (креативы внутри кампании) не должна выглядеть второй
   самостоятельной таблицей — только уровнем ниже. */
.tbl.sub { font-size: 12.5px; }
.tbl.sub th { font-size: 10.5px; }
.tbl.sub tbody tr:hover { background: transparent; }
.cell-name { font-weight: 600; white-space: nowrap; }
/* Ссылки в таблицах браузер красит своим синим — на тёмной теме он почти
   сливается с фоном. Отдаём им акцент панели. */
.tbl a, main a:not(.btn) { color: var(--accent); text-decoration: none; }
.tbl a:hover, main a:not(.btn):hover { text-decoration: underline; }
.cell-stack { display: inline-flex; flex-direction: column; gap: 3px; align-items: flex-start; }
.warn-text { color: var(--warn); }

/* Полоска выбранного бюджета: доля читается быстрее числа, а число всё
   равно есть в подсказке. */
.bar {
	display: block; width: 78px; height: 7px; margin-left: auto;
	background: var(--panel-2); border: 1px solid var(--line); border-radius: 4px; overflow: hidden;
}
.bar > span { display: block; height: 100%; background: var(--accent); }
.bar > span.near { background: var(--warn); }
.bar > span.full { background: var(--bad); }

/* --- Выбор колонок --- */
.colpick { position: relative; }
.colpick-dd {
	position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
	min-width: 220px; max-height: 340px; overflow: auto;
	background: var(--panel); border: 1px solid var(--line-strong);
	border-radius: var(--radius-s); box-shadow: var(--shadow-pop); padding: 6px;
}
.colpick-item {
	display: flex; flex-direction: row; align-items: center; gap: 8px;
	padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 13px;
}
.colpick-item:hover { background: var(--accent-soft); }
.colpick-item.fixed { opacity: .6; cursor: default; }
.colpick-item input { width: auto; }
.colpick-foot { display: flex; gap: 6px; border-top: 1px solid var(--line); margin-top: 6px; padding-top: 6px; }

/* --- Вкладки --- */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin-bottom: 18px; flex-wrap: wrap; }
.tab {
	background: none; border: none; cursor: pointer;
	padding: 9px 14px; color: var(--muted); font-weight: 600; font-size: 13.5px;
	border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tab:hover { color: var(--ink); }
.tab.on { color: var(--accent); border-bottom-color: var(--accent); }
.tab-badge {
	margin-left: 6px; background: var(--accent-soft); color: var(--accent);
	border-radius: 20px; padding: 1px 7px; font-size: 11px;
}

/* Вкладки измерений аналитики: их два десятка, поэтому сгруппированы и
   переносятся, а не сжимаются в нечитаемую полосу. */
.dim-tabs { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.dim-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dim-label {
	font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em;
	color: var(--muted); width: 116px; flex: none;
}
button.dim {
	padding: 4px 11px; border-radius: 20px; cursor: pointer; font-size: 12.5px;
	border: 1px solid var(--line); background: var(--panel); color: var(--muted);
}
button.dim:hover { color: var(--ink); border-color: var(--line-strong); }
button.dim.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }

/* --- Модальное окно --- */
.modal-back {
	position: fixed; inset: 0; z-index: 100;
	background: rgba(5, 6, 15, .55);
	display: flex; align-items: center; justify-content: center; padding: 24px;
}
.modal {
	background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow-pop);
	width: min(680px, 100%); max-height: 85vh; display: flex; flex-direction: column;
}
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid var(--line); }
.modal-head h2 { margin: 0; }
.modal-body { padding: 18px 20px; overflow: auto; }
.modal-foot { padding: 14px 20px; border-top: 1px solid var(--line); display: flex; gap: 8px; justify-content: flex-end; }
.modal-x { background: none; border: none; font-size: 22px; line-height: 1; color: var(--muted); cursor: pointer; }
.modal-x:hover { color: var(--ink); }

/* --- Диалоги --- */
/* Вопрос стоит заголовком, последствие под ним: так же устроено системное
   окно, которое здесь заменяется, и читается сверху вниз в том порядке, в
   котором принимают решение. Поэтому заголовок диалога — обычная фраза, а
   не капслок из h2 остальной панели. */
.dialog { width: min(460px, 100%); }
.dialog .modal-head { border-bottom: none; padding-bottom: 0; }
.dialog .modal-head h2 {
	font-size: 15px; text-transform: none; letter-spacing: -.01em; color: var(--ink); line-height: 1.35;
}
.dialog .modal-body { padding-top: 10px; padding-bottom: 6px; }
.dialog .modal-foot { border-top: none; }
.dialog-text { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; white-space: pre-line; }
.dialog-input { width: 100%; }
.dialog-text + .dialog-input { margin-top: 14px; }
.dialog-input.mono { font-family: var(--mono); }
/* Опасное действие красится в цвет отказа: «Удалить» и «Отмена» не должны
   выглядеть одинаково важными. */
.btn.danger { background: var(--bad); color: #fff; }

/* --- Мелочи страниц --- */
.deflist { display: grid; grid-template-columns: max-content 1fr; gap: 8px 20px; margin: 0; }
.deflist dt { color: var(--muted); font-size: 13px; }
.deflist dd { margin: 0; font-weight: 600; }

.weight-cell { display: flex; align-items: center; gap: 4px; justify-content: flex-end; }
.mod-list { margin-top: 6px; }
.ssp-desc { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.ssp-desc-row td { padding-top: 0; }

/* Живой индикатор в сайдбаре: цифры замирают одинаково и когда закупка
   встала, и когда панель потеряла связь — различить это должно быть можно
   не отходя от таблицы. */
.live-dot {
	margin-top: auto; display: flex; align-items: center; gap: 8px;
	padding: 8px 12px; font-size: 11.5px; color: #6b7488;
}
.live-dot > span {
	width: 7px; height: 7px; border-radius: 50%; background: var(--bad); flex: none;
}
.live-dot.on > span { background: #2dd9ff; box-shadow: 0 0 8px rgba(45, 217, 255, .9); }
nav.side a.logout { margin-top: 0; }

@media (max-width: 900px) {
	.toolbar-search { flex: 1 1 100%; }
	.dim-label { width: 100%; }
}

/* --- Модерация по SSP ---
   Плашки складываются в одну строку и переносятся. Ширину держит счётчик
   одобренных: сколько бы площадок ни подключили, при штатном «везде одобрено»
   в ячейке остаётся ровно одна плашка. */
.mod-chips { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.mod-chip {
	font-size: 11.5px; font-weight: 600; line-height: 1.5;
	padding: 2px 9px; border-radius: 20px; white-space: nowrap;
	border: 1px solid transparent;
}
.mod-chip.approved { background: var(--ok-soft); color: var(--ok); }
.mod-chip.pending { background: var(--warn-soft); color: var(--warn); }
.mod-chip.rejected { background: var(--bad-soft); color: var(--bad); }
.mod-chip.draft { background: var(--panel-2); color: var(--muted); border-color: var(--line); }

.mod-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.mod-dialog { display: flex; flex-direction: column; }
.mod-dialog-row {
	display: grid; grid-template-columns: 1fr auto auto;
	align-items: center; gap: 12px;
	padding: 10px 0; border-bottom: 1px solid var(--line);
}
.mod-dialog-row:last-child { border-bottom: none; }
.mod-dialog-actions { display: flex; gap: 6px; }

/* --- Сворачиваемые блоки формы --- */
.section {
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	box-shadow: var(--shadow);
	margin-bottom: 14px;
	overflow: hidden;
}
.section.nested { box-shadow: none; background: var(--panel-2); margin: 12px 0 0; }
/* Блок внутри карточки собственной рамки не заводит: две вложенные рамки со
   своими отступами съедают ширину и ничего не сообщают. Внутри карточки
   блоки идут строками во всю её ширину, разделённые линией, — так же, как
   это устроено в панелях, у которых с местом всё в порядке. */
.card.flush > .section {
	border: none; border-top: 1px solid var(--line);
	border-radius: 0; box-shadow: none; margin: 0; background: none;
}
.card.flush > .section:first-child { border-top: none; }
.section-head {
	display: flex; align-items: center; gap: 10px;
	width: 100%; padding: 14px 18px;
	background: none; border: none; cursor: pointer; text-align: left;
	color: var(--ink); font: inherit;
}
.section-head:hover { background: var(--panel-2); }
.section.nested .section-head { padding: 10px 14px; }
.section-chevron { display: grid; place-items: center; color: var(--muted); }
.section-chevron svg { width: 13px; height: 13px; transition: transform .15s ease; }
.section-chevron.open svg { transform: rotate(90deg); }
.section-title { font-weight: 600; font-size: 13.5px; }
/* Сводка справа: свёрнутый блок должен сообщать, что в нём настроено. */
.section-summary {
	margin-left: auto; color: var(--muted); font-size: 12.5px;
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 55%;
}
.section-body { padding: 4px 18px 18px; }
.section.nested .section-body { padding: 4px 14px 14px; }

.form-foot { margin-top: 4px; }
.form-actions { display: flex; gap: 8px; align-items: center; margin-top: 12px; }

/* --- Редактор суточной кривой --- */
.curve { margin-top: 14px; }
.curve-presets { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 10px; }
button.preset {
	padding: 5px 12px; border-radius: 20px; cursor: pointer; font-size: 12.5px;
	border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink);
	transition: border-color .12s, color .12s, background .12s;
}
button.preset:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.curve-tip { color: var(--muted); font-size: 12px; margin-left: 6px; }

.curve-svg {
	display: block; width: 100%; height: 150px;
	background: var(--panel-2); border: 1px solid var(--line);
	border-radius: var(--radius-s);
	cursor: crosshair; touch-action: none; user-select: none;
}
.curve-svg.drawing { cursor: grabbing; }
.curve-grid-line { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.curve-bar { fill: var(--accent); opacity: .55; transition: opacity .1s; }
.curve-bar.hot { opacity: .95; }
/* Нулевой час рисуем полоской у оси: иначе «не покупаем в этот час»
   визуально ничем не отличается от «здесь ещё ничего не задано». */
.curve-bar.zero { fill: var(--bad); opacity: .35; }
.curve-hours {
	display: grid; grid-template-columns: repeat(24, 1fr);
	font-size: 10px; color: var(--muted); margin-top: 3px; text-align: center;
}
.curve-hours .hot { color: var(--accent); font-weight: 600; }
.curve-numbers { margin-top: 12px; }
.curve-numbers summary { cursor: pointer; color: var(--muted); font-size: 12.5px; }
.curve-numbers summary:hover { color: var(--ink); }

/* --- SSP карточками --- */
.ssp-new { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.ssp-new label { flex: 1 1 280px; }
.ssp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(430px, 1fr)); gap: 14px; }
.ssp-card {
	background: var(--panel); border: 1px solid var(--line);
	border-radius: var(--radius); box-shadow: var(--shadow);
	padding: 16px 18px; display: flex; flex-direction: column; gap: 12px;
}
.ssp-card.muted { opacity: 1; }
.ssp-card.muted .ssp-title h3 { color: var(--muted); }
.ssp-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.ssp-title h3 { margin: 0; font-size: 15px; }
.ssp-flags { display: flex; gap: 6px; flex-wrap: wrap; }
.ssp-url {
	display: flex; align-items: center; gap: 8px;
	background: var(--panel-2); border: 1px solid var(--line);
	border-radius: var(--radius-s); padding: 8px 10px;
}
.ssp-url code {
	flex: 1 1 auto; min-width: 0; font-family: var(--mono); font-size: 12px;
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ssp-url button { flex: none; margin: 0; }
.ssp-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }
.ssp-fields .ssp-span { grid-column: span 2; }
.ssp-foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.ssp-switch { display: flex; flex-direction: row; align-items: center; gap: 7px; font-size: 12.5px; color: var(--muted); }

@media (max-width: 620px) {
	.ssp-grid { grid-template-columns: 1fr; }
	.ssp-fields { grid-template-columns: 1fr; }
	.ssp-fields .ssp-span { grid-column: auto; }
}

/* --- Аналитика --- */

.range-picker { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.range-custom { display: flex; align-items: flex-end; gap: 8px; }
.range-custom label { font-size: 12px; }
.range-custom input { padding: 5px 8px; }

/* Хлебные крошки проваливания по иерархии инвентаря. */
/* Крошки выровнены по содержимому карточки под ними, а не по краю
   страницы: они описывают именно её, и висеть левее заголовка таблицы им
   незачем. */
.crumbs {
	display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
	margin-bottom: 12px; padding-left: 16px; font-size: 13px;
}
.crumb-sep { color: var(--muted); margin: 0 4px; }

/* Строка, пока едет новый запрос: цифры на экране уже устарели, но убирать
   их нельзя — таблица бы прыгала на каждом переключении. */
.tbl tr.stale { opacity: .55; }
.tbl tfoot td { font-weight: 700; border-top: 2px solid var(--line-strong); background: var(--panel-2); }
.tbl th.tbl-act, .tbl td.tbl-act { width: 1%; white-space: nowrap; text-align: right; }

/* Воронка: шаги в ряд, отсев отдельным цветом слева. */
.funnel { display: flex; gap: 10px; flex-wrap: wrap; }
.funnel-step {
	flex: 1 1 150px; min-width: 150px;
	background: var(--panel-2); border: 1px solid var(--line);
	border-radius: var(--radius-s); padding: 12px 14px;
}
.funnel-step.reject { border-color: var(--bad); background: var(--bad-soft); }
.funnel-n { font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.funnel-l { font-size: 12.5px; color: var(--muted); margin-top: 2px; }

/* --- Конструктор отчётов --- */

.reports-layout { display: grid; grid-template-columns: 240px 1fr; gap: 16px; align-items: start; }
.reports-saved {
	background: var(--panel); border: 1px solid var(--line);
	border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 14px 10px;
	position: sticky; top: 22px;
}
.reports-saved .card-head { margin-bottom: 8px; }
.saved-item {
	display: flex; align-items: center; justify-content: space-between; gap: 6px;
	padding: 6px 8px; border-radius: 7px;
}
.saved-item:hover { background: var(--panel-2); }
.saved-item.on { background: var(--accent-soft); }
.saved-item .linkish { text-align: left; text-decoration: none; }
.saved-item.on .linkish { font-weight: 600; }

.picker { display: flex; flex-direction: column; gap: 10px; }
.picker-group { display: flex; align-items: flex-start; gap: 8px; }
.picker-group .dim-label { padding-top: 5px; }
.picker-items { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 auto; }

.stat-row { display: flex; gap: 8px; flex-wrap: wrap; margin: 4px 0 12px; }
.filter-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
/* Только ПРЯМЫЕ дети: всплывающий список — тоже потомок строки фильтра, и
   правило «input растягивается» доставалось его полю поиска. В flex-колонке
   панели то поле растягивалось не по ширине, а по высоте — на пол-экрана. */
.filter-row > .sel { flex: 0 0 auto; }
.filter-row > input { flex: 1 1 220px; }

@media (max-width: 900px) {
	.reports-layout { grid-template-columns: 1fr; }
	.reports-saved { position: static; }
	.picker-group { flex-direction: column; gap: 4px; }
}

/* --- Пагинатор журнала --- */
/* Живёт в подвале flush-карточки и повторяет отступы её панели фильтров,
   иначе таблица оказывается зажата между двумя элементами с разной сеткой. */
.pager {
	display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
	padding: 10px 16px; border-top: 1px solid var(--line);
	font-size: 12.5px; color: var(--muted);
}
.pager-gap { flex: 1 1 auto; }
.pager-pos { font-variant-numeric: tabular-nums; }

/* --- Полоса состояния системы на дашборде --- */
/* Дашборд открывает рекламный менеджер, а не дежурный инженер: ему нужно
   знать, что закупке ничего не мешает, а не доля времени в паузах сборщика
   мусора. Поэтому здесь одна строка — «всё в порядке» либо перечень того,
   что сломалось, — а подробности живут в разделе «Система и нагрузка». */
.sysbar {
	display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
	padding: 10px 14px; margin-bottom: 14px;
	border-radius: var(--radius); border: 1px solid var(--line);
	background: var(--panel); box-shadow: var(--shadow);
	font-size: 13px;
}
.sysbar.bad { border-color: var(--bad); background: var(--bad-soft); }
.sysbar.warn { border-color: var(--warn); background: var(--warn-soft); }
.sysbar-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--ok); }
.sysbar.warn .sysbar-dot { background: var(--warn); }
.sysbar.bad .sysbar-dot { background: var(--bad); }
.sysbar-text { color: var(--ink); }
.sysbar-metrics { display: flex; gap: 16px; flex-wrap: wrap; color: var(--muted); }
.sysbar-metrics b { color: var(--ink); font-variant-numeric: tabular-nums; }
.sysbar a { margin-left: auto; white-space: nowrap; }

/* --- Переключатель метрики на графике --- */
.chart-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.chart-head .range-tabs { margin-left: auto; }

/* --- Языки на странице входа --- */
.login-lang {
	display: flex; gap: 6px; justify-content: center;
	margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line);
}
.login-lang button {
	padding: 4px 12px; border-radius: 20px; font-size: 12.5px; cursor: pointer;
	border: 1px solid var(--line-strong); background: var(--panel); color: var(--muted);
	transition: border-color .12s, color .12s, background .12s;
}
.login-lang button:hover { border-color: var(--accent); color: var(--accent); }
.login-lang button.on { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }

/* Пара графиков в ряд: на широком экране одиночный график растягивается на
   всю ширину и превращается в полосу высотой в палец. */
.chart-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
@media (max-width: 900px) { .chart-row { grid-template-columns: 1fr; } }

/* Отступ для содержимого, которое попало во flush-карточку не таблицей. */
.card-pad { padding: 14px 16px; }

/* Два поля, читающиеся одной фразой («отсекать строки, где X меньше N»),
   стоят в строку, а пояснение — под ними во всю ширину. В сетке формы они
   разъезжались бы по колонкам, и фраза распадалась. */
.field-row { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.field-row label { flex: 0 1 auto; }
.field-row input[type="number"] { width: 130px; }
.range-picker { flex: 1 1 auto; }

/* Высоту графиков задаёт контейнер, а не соотношение сторон холста.
   Иначе кольцевая диаграмма держит квадрат и в ряду с линейным графиком
   вырастает во всю ширину карточки — ряд разъезжается по высоте. */
.chart-fixed { position: relative; height: 190px; }
.chart-main { position: relative; height: 300px; }

/* Кнопки карточки SSP переносятся вместе: поодиночке «Сохранить» уезжало на
   свою строку и подвал карточки распухал вдвое. */
.ssp-acts { display: flex; align-items: center; gap: 8px; }

/* --- Календари ---
   Системные <input type="date"/datetime-local> рисует браузер: в Chrome
   одно, в Safari другое, в тёмной теме — светлая плашка с чужими
   скруглениями. И минуты они требуют всегда, а нам они не нужны нигде. */
.dp { position: relative; display: inline-block; }
.dp-btn {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	font: inherit;
	color: var(--ink);
	padding: 8px 11px;
	border: 1px solid var(--line);
	border-radius: var(--radius-s);
	background: var(--panel);
	cursor: pointer;
	white-space: nowrap;
	transition: border-color .12s, box-shadow .12s;
}
.dp-btn:hover:not(:disabled) { border-color: var(--line-strong); }
.dp-btn:disabled { opacity: .55; cursor: default; }
.dp-btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.dp-btn.ph .dp-val { color: var(--muted); }
.dp-ico { flex: 0 0 auto; width: 14px; height: 14px; color: var(--muted); }
.dp-pop {
	position: fixed;
	z-index: 60;
	max-width: calc(100vw - 16px);
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	box-shadow: var(--shadow-pop);
	padding: 12px;
	overflow: auto;
}
.dp-body { display: flex; gap: 16px; align-items: flex-start; }
.dp-note { margin-top: 8px; font-size: 11px; color: var(--hint); }
.dp-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }
.dp-foot button {
	font: inherit; font-size: 12px; padding: 5px 12px; cursor: pointer;
	border-radius: var(--radius-s); border: 1px solid var(--line); background: var(--panel); color: var(--muted);
}
.dp-foot .dp-done { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
.dp-foot button:hover { border-color: var(--line-strong); }
.dp-foot .dp-done:hover { filter: brightness(1.06); }

.cal { width: 224px; }
.cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.cal-title { font-size: 13px; font-weight: 600; color: var(--ink); }
.cal-nav, .cal-nav-gap {
	width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;
	border: 0; background: none; color: var(--muted); font-size: 17px; line-height: 1; cursor: pointer;
	border-radius: 5px; padding: 0;
}
.cal-nav:hover { background: var(--accent-soft); color: var(--ink); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-wd { text-align: center; font-size: 10.5px; color: var(--hint); padding-bottom: 4px; }
.cal-cell {
	height: 28px; padding: 0; font: inherit; font-size: 12px;
	border: 0; border-radius: 5px; background: none; color: var(--ink); cursor: pointer;
}
.cal-cell.empty { visibility: hidden; }
.cal-cell:hover:not(:disabled):not(.empty) { background: var(--accent-soft); }
/* Сегодня помечено рамкой, а не заливкой: заливка спорила бы с выбранным. */
.cal-cell.today { box-shadow: inset 0 0 0 1px var(--line-strong); }
.cal-cell.in { background: var(--accent-soft); border-radius: 0; }
.cal-cell.on, .cal-cell.edge { background: var(--accent); color: var(--accent-ink); font-weight: 600; }
.cal-cell:disabled { opacity: .35; cursor: default; }

.cal-hours { width: 152px; }
.cal-hours-title { font-size: 13px; font-weight: 600; color: var(--ink); margin-bottom: 8px; height: 24px; display: flex; align-items: center; }
.cal-hours-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px; }
.cal-hour {
	height: 28px; padding: 0; font: inherit; font-size: 12px;
	border: 0; border-radius: 5px; background: none; color: var(--ink); cursor: pointer;
}
.cal-hour:hover { background: var(--accent-soft); }
.cal-hour.on { background: var(--accent); color: var(--accent-ink); font-weight: 600; }

@media (max-width: 720px) {
	.dp-body { flex-direction: column; }
	.dp-range .dp-pop { left: auto; right: 0; }
}

/* --- Раздел «Доступ»: люди, клиенты, матрица прав --- */

/* Строка-карточка вместо таблицы: у пользователя пять признаков и три
   действия, и в таблице действия либо уезжали в узкую колонку, либо
   растягивали строку так, что имя терялось слева от пустоты. */
.acc-row {
	display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
	padding: 10px 0; border-bottom: 1px solid var(--line);
}
.acc-row:last-child { border-bottom: 0; }
.acc-row.off { opacity: .6; }
.acc-main { flex: 1 1 240px; min-width: 0; }
.acc-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.acc-title b { font-weight: 600; }
.acc-sub { display: flex; gap: 6px; flex-wrap: wrap; color: var(--muted); font-size: 12px; margin-top: 2px; }
/* Тема коммита рядом с его идентификатором: длинная, поэтому обрезается —
   строка сборки не должна расти в высоту от чужого многословия. */
.acc-what { color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Доли креатива: заданная и фактическая рядом. Стрелка бледнее цифр —
   она разделитель, а не значение. */
.cr-split { display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap; }
.cr-split.off { opacity: .55; }
.cr-arrow { color: var(--muted); }
.acc-acts { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; flex-wrap: wrap; }
.acc-add { margin-bottom: 10px; }
.acc-add input { flex: 1 1 200px; }

.row-toggle { display: flex; align-items: center; gap: 8px; flex-direction: row; font-size: 12px; color: var(--muted); }
.pw-field .row { display: flex; gap: 8px; align-items: center; }
.pw-field input { flex: 1 1 auto; font-family: var(--mono, ui-monospace, monospace); }

/* Матрица прав: первая колонка — текст, остальные узкие и по центру. */
.matrix th:first-child, .matrix td:first-child { text-align: left; width: 42%; }
.matrix th:not(:first-child) { text-align: center; white-space: nowrap; }
.matrix td.cell-mark { text-align: center; }
.matrix .yes { color: var(--ok); font-weight: 700; }
.matrix .no { color: var(--muted); }

/* Кто вошёл — в подвале тёмного меню, прямо над «Выйти». Цвета заданы явно:
   переменные темы здесь светлые, а сайдбар тёмный в обеих темах.
   margin-top: auto переезжает сюда с .logout, иначе блок остался бы вверху. */
.who {
	margin-top: auto; padding: 10px 12px 4px;
	display: flex; flex-direction: column; gap: 1px; min-width: 0;
}
.who + a.logout { margin-top: 0; }
.who-name, .who-role { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who-name { font-size: 12px; font-weight: 600; color: var(--side-hi); }
.who-role { font-size: 11px; color: #78829a; }

/* Фильтры журнала действий: селект автора, поиск и переключатель отказов
   в одну строку. Ширины заданы напрямую потомкам — иначе правило .filter-row
   достаётся и полю поиска внутри выпадающего списка. */
/* Карточка журнала — flush: у неё нет собственных полей, отступы держит
   всё, что стоит над таблицей. Панель фильтров про это не знала и липла к
   рамке с обеих сторон. */
.card.flush > .filter-row { padding: 12px 16px; margin: 0; border-bottom: 1px solid var(--line); }
.audit-filter > .sel { flex: 0 0 auto; width: 200px; }
/* Поиску не нужна вся оставшаяся ширина: запрос здесь — слово или кусок
   пути, а растянутое на пол-экрана поле выглядит как ошибка вёрстки. */
.audit-filter > input { flex: 0 1 340px; min-width: 160px; }
.audit-filter > .row-toggle { flex: 0 0 auto; margin-left: auto; }

/* Предупреждение внутри карточки: не тост и не ErrorBox, потому что это не
   событие, а состояние — оно должно оставаться на экране, пока не пройдёт. */
.warn-line {
	margin: 8px 0 0; padding: 8px 10px; border-radius: var(--radius-s);
	background: var(--warn-soft); color: var(--warn); font-size: 13px;
}

/* Форма имени в шапке карточки: замена браузерному prompt. Живёт там же,
   где стояла кнопка, поэтому не двигает содержимое под собой. */
.name-form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.name-form input { width: 240px; max-width: 46vw; }
