/* Компоненты дизайн-системы витрины (docs/10-design-system.md §3).
   Один компонент — один блок ds-<имя>; только var(--ds-*) из tokens.css. */

.ds-page {
  max-width: var(--ds-page-w);
  margin: 0 auto;
  padding: var(--ds-space-5) var(--ds-space-5) 140px;
  font-size: var(--ds-fs-body);
  line-height: 1.45;
}

.ds-num { font-variant-numeric: tabular-nums; text-align: right; }
.ds-tone--ok { color: var(--ds-accent); }
.ds-tone--warn { color: var(--ds-warn); }
.ds-tone--bad { color: var(--ds-danger); }
.ds-tone--muted { color: var(--ds-ink-2); }
.ds-tone--none { color: var(--ds-ink-3); }

/* --- шапка ------------------------------------------------------------- */

.ds-topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: var(--ds-space-2) var(--ds-space-6);
  flex-wrap: wrap;
  padding: 0 var(--ds-space-5);
  min-height: 56px;
  background: var(--ds-surface);
  border-bottom: 1px solid var(--ds-line);
}

.ds-topbar__brand { font-weight: 700; font-size: var(--ds-fs-body); color: var(--ds-ink); text-decoration: none; letter-spacing: -.01em; }
.ds-topbar__nav { display: flex; gap: var(--ds-space-5); }

.ds-topbar__link {
  display: inline-flex;
  align-items: center;
  min-height: 56px;
  color: var(--ds-ink-2);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.ds-topbar__link:hover { color: var(--ds-ink); }
.ds-topbar__link--active { color: var(--ds-ink); border-bottom-color: var(--ds-accent); }
.ds-topbar__side { margin-left: auto; display: flex; align-items: center; gap: var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-topbar__logout { min-height: 40px; padding: 0 var(--ds-space-2); border: 0; background: none; color: var(--ds-ink-2); font: inherit; cursor: pointer; }
.ds-topbar__logout:hover { color: var(--ds-ink); }

/* --- заголовок экрана -------------------------------------------------- */

.ds-head { display: flex; align-items: flex-start; gap: var(--ds-space-4) var(--ds-space-5); flex-wrap: wrap; margin-bottom: var(--ds-space-4); }
.ds-head__naming { min-width: 0; flex: 1 1 320px; }
.ds-head__crumbs { margin: 0 0 var(--ds-space-1); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-head__crumbs a { color: var(--ds-ink-2); text-decoration: none; }
.ds-head__crumbs a:hover { color: var(--ds-ink); }
.ds-head__title { margin: 0; font-size: var(--ds-fs-title); font-weight: 700; letter-spacing: -.01em; line-height: 1.15; }
.ds-head__meta { margin: var(--ds-space-2) 0 0; display: flex; flex-wrap: wrap; gap: var(--ds-space-1) var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; }
.ds-head__actions { display: flex; gap: var(--ds-space-2); flex-wrap: wrap; align-items: center; }
.ds-head__totals { display: flex; gap: var(--ds-space-5); flex-wrap: wrap; margin: var(--ds-space-3) 0 0; }

/* --- атомы ------------------------------------------------------------- */

.ds-stat { display: inline-flex; flex-direction: column; align-items: flex-end; font-variant-numeric: tabular-nums; line-height: 1.15; }
.ds-stat__value { font-weight: 600; color: var(--ds-ink); }
.ds-stat--card .ds-stat__value { font-size: var(--ds-fs-stat-card); font-weight: 700; letter-spacing: -.01em; }
.ds-stat--row .ds-stat__value { font-size: var(--ds-fs-stat-row); }
.ds-stat--cabinet .ds-stat__value { font-size: var(--ds-fs-stat-cabinet); }
.ds-stat__label { font-size: var(--ds-fs-caption); color: var(--ds-ink-2); font-weight: 400; }
.ds-stat--empty .ds-stat__value { color: var(--ds-ink-3); font-weight: 400; }
.ds-stat--ok .ds-stat__value { color: var(--ds-accent); }
.ds-stat--warn .ds-stat__value { color: var(--ds-warn); }
.ds-stat--bad .ds-stat__value, .ds-stat--danger .ds-stat__value { color: var(--ds-danger); }

.ds-delta { font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ds-delta--ok { color: var(--ds-accent); }
.ds-delta--bad { color: var(--ds-danger); }
.ds-delta--warn { color: var(--ds-warn); }
.ds-delta--muted { color: var(--ds-ink-2); }
.ds-delta__note { color: var(--ds-ink-2); }

.ds-verdict { display: inline-flex; flex-direction: column; line-height: 1.3; }
.ds-verdict__word { font-weight: 500; }
.ds-verdict__fact { font-size: var(--ds-fs-caption); color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-verdict--ok .ds-verdict__word { color: var(--ds-accent); }
.ds-verdict--warn .ds-verdict__word { color: var(--ds-warn); }
.ds-verdict--bad .ds-verdict__word { color: var(--ds-danger); }
.ds-verdict--muted .ds-verdict__word { color: var(--ds-ink-2); font-weight: 400; }
.ds-verdict--none .ds-verdict__word { color: var(--ds-ink-3); font-weight: 400; }

.ds-spark { display: block; overflow: visible; }
.ds-spark__line { fill: none; stroke: var(--ds-ink-2); stroke-width: 1.4; stroke-linejoin: round; stroke-linecap: round; }
.ds-spark__line--immature { stroke-dasharray: 3 3; stroke: var(--ds-ink-3); }
.ds-spark__dot--leads { fill: var(--ds-accent); stroke: var(--ds-surface); stroke-width: 1; }
.ds-spark__dot--none { fill: var(--ds-surface); stroke: var(--ds-danger); stroke-width: 1.4; }

.ds-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  min-height: var(--ds-chip-h);
  padding: 0 var(--ds-space-3);
  border: 1px solid var(--ds-line-2);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-surface);
  color: var(--ds-ink);
  font-size: var(--ds-fs-caption);
  text-decoration: none;
  white-space: nowrap;
}

.ds-chip:hover { border-color: var(--ds-ink-2); }
.ds-chip--active { background: var(--ds-ink); border-color: var(--ds-ink); color: var(--ds-surface); }
.ds-chip__count { color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-chip--active .ds-chip__count { color: var(--ds-line-2); }

.ds-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--ds-ink-3); vertical-align: middle; flex: 0 0 auto; }
.ds-dot--ok { background: var(--ds-accent); }
.ds-dot--warn { background: var(--ds-warn); }
.ds-dot--bad { background: var(--ds-danger); }

.ds-star { display: inline-flex; margin: 0; }
.ds-star__button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; margin: -11px 0 -11px -11px; padding: 0; border: 0; background: none; cursor: pointer; color: var(--ds-line-2); }
.ds-star__button svg { fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linejoin: round; }
.ds-star__button--on { color: var(--ds-warn); }
.ds-star__button--on svg { fill: currentColor; }
.ds-star__button:hover { color: var(--ds-warn); }

.ds-snapshot { display: inline-flex; flex-direction: column; gap: 2px; font-variant-numeric: tabular-nums; line-height: 1.3; }
.ds-snapshot__label { color: var(--ds-ink); }
.ds-snapshot--none .ds-snapshot__label { color: var(--ds-ink-3); }
.ds-snapshot--stale .ds-snapshot__label { align-self: flex-start; padding: 1px 8px; border-radius: var(--ds-radius-pill); background: var(--ds-warn-bg); border: 1px solid var(--ds-warn-line); color: var(--ds-warn); font-size: var(--ds-fs-caption); }
.ds-snapshot--failed .ds-snapshot__label { color: var(--ds-danger); font-weight: 500; }
.ds-snapshot__reason { font-size: var(--ds-fs-label); color: var(--ds-ink-2); max-width: 22ch; }
.ds-snapshot__go { font-size: var(--ds-fs-caption); color: var(--ds-accent); text-decoration: none; }
.ds-snapshot__go:hover { text-decoration: underline; }

.ds-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--ds-control-h);
  padding: 0 var(--ds-space-4);
  border: 1px solid transparent;
  border-radius: var(--ds-radius-s);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  background: none;
}

.ds-button--primary { background: var(--ds-accent); border-color: var(--ds-accent); color: #ffffff; }
.ds-button--primary:hover { background: var(--ds-accent-dark); border-color: var(--ds-accent-dark); }
.ds-button--outline { border-color: var(--ds-accent); color: var(--ds-accent); background: var(--ds-surface); }
.ds-button--outline:hover { background: var(--ds-surface-2); }
.ds-button--outline-muted { border-color: var(--ds-line-2); color: var(--ds-ink-2); background: var(--ds-surface); }
.ds-button--outline-muted:hover { color: var(--ds-ink); border-color: var(--ds-ink-2); }
.ds-button--link { color: var(--ds-accent); padding: 0; min-height: 0; }
.ds-button--link:hover { text-decoration: underline; }
.ds-button--row { min-height: 38px; }
.ds-button--s { min-height: 34px; padding: 0 var(--ds-space-3); font-size: var(--ds-fs-caption); }
.ds-button--disabled { opacity: .55; cursor: default; }
.ds-button__form { display: inline; margin: 0; }

.ds-pill-switch { display: inline-flex; padding: 3px; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill); background: var(--ds-surface); }
.ds-pill-switch__item { display: inline-flex; align-items: center; min-height: 28px; padding: 0 var(--ds-space-3); border-radius: var(--ds-radius-pill); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); text-decoration: none; }
.ds-pill-switch__item--active { background: var(--ds-ink); color: var(--ds-surface); }
.ds-pill-switch__item:not(.ds-pill-switch__item--active):hover { color: var(--ds-ink); }

.ds-search { position: relative; display: inline-flex; align-items: center; min-width: 240px; flex: 1 1 240px; max-width: 360px; }
.ds-search__icon { position: absolute; left: 12px; color: var(--ds-ink-3); pointer-events: none; }
.ds-search__sr { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.ds-search__input { width: 100%; min-height: 38px; padding: 0 var(--ds-space-3) 0 36px; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); font: inherit; font-size: 14px; color: var(--ds-ink); }
.ds-search__input::placeholder { color: var(--ds-ink-3); }

/* --- молекулы ----------------------------------------------------------- */

.ds-daybrief { margin: 0 0 var(--ds-space-4); font-size: var(--ds-fs-body); color: var(--ds-ink-2); }
.ds-daybrief__part { color: var(--ds-ink); text-decoration: none; }
.ds-daybrief__part:hover { text-decoration: underline; }
.ds-daybrief__num { font-weight: 700; font-variant-numeric: tabular-nums; }
.ds-daybrief__num--ok { color: var(--ds-accent); }
.ds-daybrief__num--warn { color: var(--ds-warn); }
.ds-daybrief__num--bad { color: var(--ds-danger); }

.ds-statcards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--ds-space-3); margin: 0 0 var(--ds-space-5); }

.ds-statcard { display: flex; flex-direction: column; gap: var(--ds-space-1); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); min-width: 0; }
.ds-statcard .ds-stat { align-items: flex-start; }
.ds-statcard--danger { background: var(--ds-danger-bg); border-color: var(--ds-danger-line); }
.ds-statcard__label { margin: 0; font-size: var(--ds-fs-label); letter-spacing: .06em; text-transform: uppercase; color: var(--ds-ink-2); }
.ds-statcard__line { margin: 0; display: flex; gap: var(--ds-space-2); flex-wrap: wrap; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-statcard__split { margin: 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-statcard__spark { margin-top: auto; padding-top: var(--ds-space-2); }

.ds-source { list-style: none; margin: 0; padding: 0; font-size: var(--ds-fs-caption); line-height: 1.35; font-variant-numeric: tabular-nums; }
.ds-source--empty { color: var(--ds-ink-3); }
.ds-source__model { color: var(--ds-ink-2); }
.ds-source__figures { color: var(--ds-ink); }
.ds-source__line--ok .ds-source__verdict { color: var(--ds-accent); }
.ds-source__line--warn .ds-source__verdict { color: var(--ds-warn); }
.ds-source__line--bad .ds-source__verdict { color: var(--ds-danger); }
.ds-source__line--bad .ds-source__figures { color: var(--ds-danger); }

.ds-money { display: inline-flex; flex-direction: column; line-height: 1.3; font-variant-numeric: tabular-nums; }
.ds-money__value { font-weight: 500; }
.ds-money__caption { font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-money--bad .ds-money__value { color: var(--ds-danger); }
.ds-money--warn .ds-money__value { color: var(--ds-warn); }
.ds-money--ok .ds-money__value { color: var(--ds-ink); }
.ds-money--none .ds-money__value { color: var(--ds-ink-3); font-weight: 400; }

.ds-tasks { list-style: none; margin: 0; padding: 0; font-size: var(--ds-fs-caption); line-height: 1.4; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ds-tasks__item { display: flex; align-items: center; gap: var(--ds-space-2); }
.ds-tasks--empty { color: var(--ds-ink-3); }

.ds-brief-strip { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ds-space-3); margin: 0 0 var(--ds-space-5); }

.ds-taskcard { position: relative; display: flex; flex-direction: column; gap: var(--ds-space-1); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-muted-bg); min-width: 0; }
.ds-taskcard--danger { background: var(--ds-danger-bg); border-color: var(--ds-danger-line); }
.ds-taskcard--warn { background: var(--ds-warn-bg); border-color: var(--ds-warn-line); }
.ds-taskcard--red-line { box-shadow: inset 3px 0 0 var(--ds-danger); }
.ds-taskcard__title { margin: 0; font-size: var(--ds-fs-section); font-weight: 600; }
.ds-taskcard--muted .ds-taskcard__title { color: var(--ds-ink-2); font-weight: 500; }
.ds-taskcard__caption { margin: 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-taskcard__action { margin-top: auto; padding-top: var(--ds-space-2); }
.ds-taskcard__word { margin: auto 0 0; padding-top: var(--ds-space-2); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }

.ds-modelverdict { margin: 0 0 var(--ds-space-5); }
.ds-modelverdict__title { margin: 0 0 var(--ds-space-2); font-size: var(--ds-fs-section); font-weight: 600; }
.ds-modelverdict__scroll { overflow-x: auto; border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-modelverdict__table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.ds-modelverdict__table th { padding: 10px 14px; text-align: left; font-size: var(--ds-fs-label); font-weight: 400; text-transform: uppercase; letter-spacing: .06em; color: var(--ds-ink-2); border-bottom: 1px solid var(--ds-line); white-space: nowrap; }
.ds-modelverdict__table td { padding: 12px 14px; border-bottom: 1px solid var(--ds-line); vertical-align: top; }
.ds-modelverdict__table tr:last-child td { border-bottom: 0; }
.ds-modelverdict__model { font-weight: 500; }
.ds-modelverdict__empty { margin: 0; color: var(--ds-ink-2); }

.ds-empty { padding: var(--ds-space-6) var(--ds-space-5); text-align: center; border: 1px dashed var(--ds-line-2); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-empty__text { margin: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-body); }
.ds-empty__action { margin-top: var(--ds-space-4); }

/* --- панель фильтров ----------------------------------------------------- */

.ds-filterbar { display: flex; align-items: center; gap: var(--ds-space-3); flex-wrap: wrap; margin: 0 0 var(--ds-space-3); }
.ds-filterbar__form { display: flex; align-items: center; gap: var(--ds-space-3); flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }
.ds-filterbar__chips { display: flex; gap: var(--ds-space-2); flex-wrap: wrap; list-style: none; margin: 0; padding: 0; }
.ds-filterbar__side { display: flex; align-items: center; gap: var(--ds-space-3); margin-left: auto; }
.ds-filterbar__sort { position: relative; }
.ds-filterbar__sort-summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; min-height: var(--ds-chip-h); padding: 0 var(--ds-space-3); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill); background: var(--ds-surface); font-size: var(--ds-fs-caption); color: var(--ds-ink); white-space: nowrap; }
.ds-filterbar__sort-summary::-webkit-details-marker { display: none; }
.ds-filterbar__sort-list { position: absolute; right: 0; top: calc(100% + 4px); z-index: 4; min-width: 200px; margin: 0; padding: var(--ds-space-1); list-style: none; background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); box-shadow: var(--ds-shadow); }
.ds-filterbar__sort-item { display: block; padding: 8px var(--ds-space-3); border-radius: 6px; color: var(--ds-ink); text-decoration: none; font-size: 14px; }
.ds-filterbar__sort-item:hover { background: var(--ds-surface-2); }
.ds-filterbar__sort-item--active { font-weight: 600; color: var(--ds-accent); }

/* --- таблица уровня ------------------------------------------------------ */

.ds-table { margin: 0 0 var(--ds-space-5); }
.ds-table__scroll { overflow-x: auto; }
.ds-table__grid { width: 100%; border-collapse: separate; border-spacing: 0; }
.ds-table__th { padding: 0 12px 8px; text-align: left; font-size: var(--ds-fs-label); font-weight: 400; letter-spacing: .06em; text-transform: uppercase; color: var(--ds-ink-2); white-space: nowrap; vertical-align: bottom; }
.ds-table__th.ds-num { text-align: right; }
.ds-table__th--action { width: 1%; }
.ds-table__sort { color: inherit; text-decoration: none; }
.ds-table__sort:hover { color: var(--ds-ink); }
.ds-table__sort--active { color: var(--ds-ink); font-weight: 600; }
.ds-table__foot { margin: var(--ds-space-3) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-table__more { color: var(--ds-accent); text-decoration: none; font-weight: 500; }
.ds-table__more:hover { text-decoration: underline; }
.ds-table__empty { padding: var(--ds-space-5); color: var(--ds-ink-2); text-align: center; }

.ds-row__cell { padding: 18px 12px; background: var(--ds-surface); border-top: 1px solid var(--ds-line); border-bottom: 1px solid var(--ds-line); vertical-align: top; }
.ds-row__cell:first-child { border-left: 3px solid transparent; border-radius: var(--ds-radius) 0 0 var(--ds-radius); }
.ds-row__cell:last-child { border-right: 1px solid var(--ds-line); border-radius: 0 var(--ds-radius) var(--ds-radius) 0; }
.ds-table .ds-row { box-shadow: none; }
.ds-table .ds-row__cell { border-top-width: 1px; }
.ds-table tbody::before { content: ""; display: block; height: 0; }
.ds-table .ds-row--project > .ds-row__cell { border-top: 1px solid var(--ds-line); }
.ds-table .ds-row__expand-row + .ds-row > .ds-row__cell,
.ds-table .ds-row + .ds-row > .ds-row__cell { border-top: 0; }
.ds-row--changed .ds-row__cell:first-child { border-left-color: var(--ds-warn); }
.ds-row--dashed .ds-row__cell { border-style: dashed; }
.ds-row--nodata .ds-row__name { color: var(--ds-ink-2); }
.ds-row--inner .ds-row__cell { background: transparent; padding: 10px 12px; }
.ds-row--inner .ds-row__cell:first-child, .ds-row--inner .ds-row__cell:last-child { border-radius: 0; border-left: 0; border-right: 0; }
.ds-row--total .ds-row__cell { background: transparent; font-weight: 600; border-top: 1px solid var(--ds-line-2); }
.ds-row--total .ds-row__cell:first-child, .ds-row--total .ds-row__cell:last-child { border-radius: 0; border-left: 0; border-right: 0; }
.ds-row--campaign .ds-row__cell { background: transparent; padding: 10px 12px; }
.ds-row--campaign .ds-row__cell:first-child, .ds-row--campaign .ds-row__cell:last-child { border-radius: 0; border-left: 0; border-right: 0; }
.ds-row__cell.ds-num { text-align: right; font-variant-numeric: tabular-nums; }
.ds-row__cell.ds-num .ds-stat { width: 100%; }
.ds-row__cell--nodata { color: var(--ds-ink-2); }
.ds-row__cell--muted { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-row__cell--action { text-align: right; white-space: nowrap; }
.ds-row__cell--spark { width: 1%; }
.ds-row__lead { display: flex; align-items: flex-start; gap: var(--ds-space-2); min-width: 0; }
.ds-row__naming { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ds-row__name { font-size: var(--ds-fs-body); font-weight: 600; color: var(--ds-ink); text-decoration: none; overflow-wrap: anywhere; }
.ds-row__name:hover { text-decoration: underline; }
.ds-row__name--plain { font-weight: 500; font-size: 14px; }
.ds-row__meta { font-size: var(--ds-fs-caption); color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-row__changed { font-size: var(--ds-fs-label); color: var(--ds-warn); }
.ds-row__sub { display: block; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ds-row__expand-cell { padding: 0 0 var(--ds-space-2); background: transparent; }
.ds-row__expand-row:last-child .ds-row__expand-cell { padding-bottom: 0; }


/* --- раскрытие ----------------------------------------------------------- */

.ds-expand { margin: 0 0 0 3px; border: 1px solid var(--ds-line); border-top: 0; border-radius: 0 0 var(--ds-radius) var(--ds-radius); background: var(--ds-surface-2); }
.ds-expand__summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: var(--ds-space-2); min-height: 40px; padding: 0 var(--ds-space-4); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-expand__summary::-webkit-details-marker { display: none; }
.ds-expand__summary::before { content: "▸"; font-size: var(--ds-fs-label); }
.ds-expand[open] > .ds-expand__summary::before { content: "▾"; }
.ds-expand__summary:hover { color: var(--ds-ink); }
.ds-expand__body { padding: 0 var(--ds-space-4) var(--ds-space-3); }
.ds-expand__table { width: 100%; border-collapse: collapse; font-size: 14px; }
.ds-expand__table .ds-table__th { padding: 6px 12px; }
.ds-expand__group { margin-top: var(--ds-space-3); }
.ds-expand__group-title { margin: 0 0 var(--ds-space-1); font-size: 14px; font-weight: 600; }
.ds-expand__foot { margin: var(--ds-space-2) 0 0; font-size: var(--ds-fs-caption); }
.ds-expand__open { color: var(--ds-accent); text-decoration: none; font-weight: 500; }
.ds-expand__open:hover { text-decoration: underline; }

/* --- строка команды ------------------------------------------------------- */

.ds-askbar { position: sticky; bottom: 0; z-index: 4; margin: var(--ds-space-5) calc(-1 * var(--ds-space-5)) -140px; padding: var(--ds-space-3) var(--ds-space-5) var(--ds-space-3); background: var(--ds-bg); }
.ds-askbar__form { display: flex; align-items: center; gap: var(--ds-space-2); min-height: var(--ds-askbar-h); padding: 6px 6px 6px var(--ds-space-3); background: var(--ds-surface); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius); box-shadow: var(--ds-shadow); }
.ds-askbar__context { display: inline-flex; align-items: center; min-height: 34px; padding: 0 var(--ds-space-3); border-radius: var(--ds-radius-pill); background: var(--ds-surface-2); font-size: var(--ds-fs-caption); color: var(--ds-ink); white-space: nowrap; }
.ds-askbar__field { flex: 1 1 auto; min-width: 0; }
.ds-askbar__input { width: 100%; min-height: 40px; border: 0; background: none; font: inherit; font-size: var(--ds-fs-body); color: var(--ds-ink); }
.ds-askbar__input::placeholder { color: var(--ds-ink-3); }
.ds-askbar__input:focus { outline: none; }
.ds-askbar__form:focus-within { border-color: var(--ds-accent); }
.ds-askbar__kbd { font: inherit; font-size: var(--ds-fs-label); color: var(--ds-ink-3); padding: 2px 6px; border: 1px solid var(--ds-line); border-radius: 6px; }
.ds-askbar__hint { margin: var(--ds-space-2) 0 0; font-size: var(--ds-fs-label); color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-askbar__hint a { color: var(--ds-ink-2); }

/* --- старый отчёт парка под сворачиванием ---------------------------------- */

.ds-fullreport { margin: var(--ds-space-5) 0 0; border-top: 1px solid var(--ds-line); }
.ds-fullreport__summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: var(--ds-space-2); min-height: 48px; font-size: var(--ds-fs-section); font-weight: 600; }
.ds-fullreport__summary::-webkit-details-marker { display: none; }
.ds-fullreport__summary::before { content: "▸"; color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-fullreport[open] > .ds-fullreport__summary::before { content: "▾"; }
.ds-fullreport__note { margin-left: auto; font-size: var(--ds-fs-caption); font-weight: 400; color: var(--ds-ink-2); }

.ds-section-title { margin: 0 0 var(--ds-space-2); font-size: var(--ds-fs-section); font-weight: 600; }

/* --- адаптив --------------------------------------------------------------- */

@media (max-width: 1100px) {
  .ds-statcards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ds-brief-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ds-table .ds-row__cell { padding: 9px 12px; }
  .ds-table .ds-stat--row .ds-stat__value, .ds-table .ds-stat--cabinet .ds-stat__value { font-size: var(--ds-fs-body); }
  .ds-table .ds-row__name { font-size: 14px; }
}

@media (max-width: 720px) {
  .ds-page { padding: var(--ds-space-4) var(--ds-space-3) 140px; }
  .ds-topbar { padding: 0 var(--ds-space-3); gap: 0 var(--ds-space-4); }
  .ds-topbar__nav { gap: var(--ds-space-4); }
  .ds-head__title { font-size: 22px; }
  .ds-statcards, .ds-brief-strip { grid-template-columns: 1fr; }
  .ds-filterbar__side { margin-left: 0; }
  .ds-search { max-width: none; }
  .ds-askbar { margin-left: calc(-1 * var(--ds-space-3)); margin-right: calc(-1 * var(--ds-space-3)); padding-left: var(--ds-space-3); padding-right: var(--ds-space-3); }
  .ds-askbar__kbd, .ds-askbar__context { display: none; }
  /* Панель пакета на телефоне: кнопки во всю ширину и под палец (44px —
     минимальная цель нажатия), счётчик отдельной строкой над ними. */
  .ds-campaign-batch { gap: var(--ds-space-2); }
  .ds-campaign-batch__count { flex: 1 0 100%; }
  .ds-campaign-batch .ds-button { flex: 1 1 auto; min-height: 44px; }
  /* На телефоне поля условия идут по одному в строку, а выпадающий список
     растягивается по ширине блока. Своей ширины у него иначе нет: браузер
     меряет её по самому длинному варианту («были в прошлом окне, а теперь
     нет»), и на 390px список занимал почти всю строку, оставляя полю N
     полтора сантиметра. */
  /* Кнопка решения на телефоне: подпись у неё длинная и осмысленная
     («Подтверждаю: остановить · 2 кампании в 2 кабинетах») — резать её
     нельзя, а уезжать за край экрана тем более. Пусть переносится. */
  .ds-campaign-confirm__decision form, .ds-campaign-confirm__decision .ds-button { max-width: 100%; }
  .ds-campaign-confirm__decision .ds-button { white-space: normal; text-align: center; padding-top: var(--ds-space-2); padding-bottom: var(--ds-space-2); }
  .ds-select-form { gap: var(--ds-space-3); padding: var(--ds-space-3); }
  .ds-select-form__block { flex: 1 0 100%; }
  .ds-select-form__field { flex: 1 1 100%; max-width: 100%; }
  .ds-select-form__field select { flex: 1 1 auto; min-width: 0; max-width: 100%; }
  .ds-select-form__go { margin-left: 0; flex: 1 0 100%; }
  .ds-select-form__go .ds-button { flex: 1 1 auto; min-height: 44px; }
  /* Заголовок группы на телефоне: имя кабинета, сводка и «Выбрать все» —
     каждое своей строкой. В один ряд они не помещаются. */
  .ds-select-group__head { gap: var(--ds-space-2); }
  .ds-select-group__head .ds-section-title { flex: 1 0 100%; }
  .ds-select-group__meta { flex: 1 0 100%; }
  .ds-select-group__head .ds-button { margin-left: 0; flex: 1 1 auto; min-width: 0; min-height: 44px; }
  /* На телефоне четыре глагола становятся сеткой два на два: подписи у них
     короткие («Остановить»), палец один, цель нажатия — не меньше 44px.
     ``min-width: 0`` обязателен: без него флекс-элемент не даёт себя сжать
     уже содержимого, и две кнопки в ряд на 390px перестают помещаться. */
  .ds-select-actions { gap: var(--ds-space-2); }
  .ds-select-actions .ds-button { flex: 1 1 45%; min-width: 0; min-height: 44px; }
  .ds-select-actions__note, .ds-select-actions__warn { flex: 1 0 100%; }
  /* У ПАНЕЛИ подписи длинные («Перезапустить выбранные»), и вдвоём в ряд они
     не помещаются ни при каком сжатии — там кнопки идут по одной в строку. */
  .ds-select-panel .ds-button { flex: 1 0 100%; min-width: 0; min-height: 44px; }
}

@media (prefers-reduced-motion: no-preference) {
  .ds-button, .ds-chip, .ds-topbar__link, .ds-star__button { transition: color .12s, background-color .12s, border-color .12s; }
}

/* --- Строка команды (askbar с формой команды, план #118 §4) -------------- */
.ds-askbar__form--command { flex-direction: column; align-items: stretch; gap: var(--ds-space-1); padding-bottom: var(--ds-space-2); }
.ds-askbar__line { display: flex; align-items: center; gap: var(--ds-space-2); min-width: 0; }
.ds-askbar__line--chips { flex-wrap: wrap; padding: 0 var(--ds-space-1); }
.ds-askbar__chips { display: inline-flex; flex-wrap: wrap; gap: var(--ds-space-1); }
.ds-chip--button { font: inherit; font-size: var(--ds-fs-caption); cursor: pointer; }
.ds-askbar__period { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; font-size: var(--ds-fs-label); color: var(--ds-ink-2); }
.ds-askbar__period-label { letter-spacing: .06em; text-transform: uppercase; }
.ds-askbar__period input[type=date] { font: inherit; font-size: var(--ds-fs-caption); padding: 3px 6px; border: 1px solid var(--ds-line); border-radius: 6px; background: var(--ds-surface); color: var(--ds-ink); }
.ds-askbar__dash { color: var(--ds-ink-3); }
.ds-askbar__errors { margin: 0; padding: 0 var(--ds-space-2); font-size: var(--ds-fs-caption); color: var(--ds-bad, #b42318); }
.ds-askbar__notice { margin: 0; padding: var(--ds-space-2) var(--ds-space-3); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius); background: var(--ds-surface); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }

/* --- Последний ответ и история под шапкой проекта ----------------------- */
.ds-answers-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-1) var(--ds-space-2); margin: calc(-1 * var(--ds-space-2)) 0 var(--ds-space-4); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-answers-line__label { letter-spacing: .06em; text-transform: uppercase; font-size: var(--ds-fs-label); }
.ds-answers-line__sep { color: var(--ds-ink-3); }
.ds-answers-line__gist { color: var(--ds-ink); }
.ds-answers { display: inline; }
.ds-answers__summary { display: inline; cursor: pointer; color: var(--ds-ink-2); }
.ds-answers__list { list-style: none; margin: var(--ds-space-2) 0 0; padding: var(--ds-space-2) var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); flex-basis: 100%; }
.ds-answers__item { display: flex; flex-wrap: wrap; gap: var(--ds-space-2); padding: 4px 0; }
.ds-answers__meta { color: var(--ds-ink-3); font-variant-numeric: tabular-nums; }

/* Строка вопроса без адресата (Парк): выключена, не притворяется рабочей. */
.ds-askbar__form--disabled { opacity: 0.6; }
.ds-askbar__form--disabled .ds-askbar__input { cursor: not-allowed; }

/* Идущий сбор (#121): место кнопки занято подписью со ссылкой на ход. */
.ds-running { display: inline-flex; align-items: center; gap: var(--ds-space-2); flex-wrap: wrap; }
.ds-running__go { font-size: var(--ds-fs-caption); color: var(--ds-accent); text-decoration: none; white-space: nowrap; }
.ds-running__go:hover { text-decoration: underline; }
.ds-running-plate { margin-bottom: var(--ds-space-4); }
.ds-running-plate a { color: var(--ds-accent); }
.ds-chip--disabled { opacity: .55; cursor: default; }
.ds-askbar__hint--running a { color: var(--ds-accent); }

.ds-stats-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ds-space-4); margin-bottom: var(--ds-space-4); }
.ds-stats-head__crumbs { margin: 0 0 var(--ds-space-1); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-stats-head__crumbs a { color: var(--ds-accent); }
/* Возврат с экрана решения: та же строка крошек, но заметнее соседей — это дорога назад, а не адрес. */
.ds-back-link { font-weight: 600; }
.ds-stats-head__title { margin: 0; font-size: var(--ds-fs-title); letter-spacing: -.01em; }
.ds-stats-head__subtitle { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); }
.ds-stats-head__collection { display: flex; align-items: baseline; gap: var(--ds-space-2); margin: var(--ds-space-2) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-stats-head__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--ds-space-2); }
.ds-stats-head__actions form { margin: 0; }
.ds-weekly-refill { display: inline-flex; align-items: center; gap: var(--ds-space-2); }
.ds-weekly-refill > span { color: var(--ds-ink-2); font-size: var(--ds-fs-label); font-variant-numeric: tabular-nums; }
.ds-period { display: flex; align-items: center; flex-wrap: wrap; gap: var(--ds-space-3); margin-bottom: var(--ds-space-4); }
.ds-period__choices { display: flex; flex-wrap: wrap; gap: var(--ds-space-1); }
.ds-period__custom { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2); }
.ds-period__custom-label { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-period__custom--active .ds-period__custom-label { color: var(--ds-ink); font-weight: 600; }
.ds-period__field { display: flex; align-items: center; gap: var(--ds-space-1); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-period__field input, .ds-stats-filters select { min-height: 40px; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); color: var(--ds-ink); font: inherit; padding: 0 var(--ds-space-2); }
.ds-metric-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--ds-space-3); margin-bottom: var(--ds-space-5); }
.ds-metric { min-width: 0; padding: var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-metric__label, .ds-metric__delta { display: block; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
/* За какие даты посчитано число и какой хвост ещё пополнится. Без этой строки
   шапка и график давали два разных числа заявок и ничего не объясняли. */
.ds-metric__window { display: block; margin-top: 2px; color: var(--ds-ink-3); font-size: var(--ds-fs-label); }
/* Подпись «чем посчитано»: версия правила заявок и день оценки. Отделена от
   строк окна, потому что отвечает на другой вопрос — не «за какие даты», а
   «каким правилом». Молчание об этом читалось как «витрина врёт» (06.09). */
.ds-metric__basis { margin-top: var(--ds-space-1); font-style: italic; }
.ds-metric__value { display: block; margin-top: var(--ds-space-1); font-size: 24px; font-variant-numeric: tabular-nums; }
.ds-metric__verdict { display: block; margin-top: var(--ds-space-2); font-size: var(--ds-fs-section); }
.ds-metric--ok .ds-metric__verdict { color: var(--ds-accent); }
.ds-metric--warn .ds-metric__verdict { color: var(--ds-warn); }
.ds-metric--bad .ds-metric__verdict { color: var(--ds-danger); }
.ds-metric__detail { display: block; margin-top: var(--ds-space-1); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-lead-breakdown { position: relative; display: inline-block; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); text-align: left; white-space: nowrap; }
.ds-lead-breakdown summary { cursor: help; list-style: none; text-decoration: underline dotted; text-underline-offset: 2px; }
.ds-lead-breakdown summary::-webkit-details-marker { display: none; }
.ds-lead-breakdown__detail { position: absolute; z-index: 3; left: 0; top: calc(100% + 4px); width: max-content; max-width: min(360px, 80vw); padding: var(--ds-space-2); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); box-shadow: var(--ds-shadow); color: var(--ds-ink); white-space: normal; }
.ds-collection { display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: var(--ds-space-1); }
.ds-collection--inline { display: inline; }
.ds-collection__reason { color: var(--ds-ink-2); }
.ds-collection--ok .ds-collection__label { color: var(--ds-accent); }
.ds-collection--warn .ds-collection__label { color: var(--ds-warn); }
.ds-collection--bad .ds-collection__label { color: var(--ds-danger); }
.ds-stats-filters { display: flex; align-items: center; flex-wrap: wrap; gap: var(--ds-space-2); margin-bottom: var(--ds-space-3); }
.ds-stats-chipbar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--ds-space-3); margin-bottom: var(--ds-space-3); }
.ds-stats-chipbar nav { display: flex; flex-wrap: wrap; gap: var(--ds-space-1); }
.ds-stats-filters .ds-search { flex: 1 1 280px; }
.ds-stats-filters__field { display: flex; align-items: center; gap: var(--ds-space-1); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-stats-table { margin-bottom: var(--ds-space-5); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); overflow: hidden; }
.ds-stats-table__scroll, .ds-campaign-chart__scroll { overflow-x: auto; }
.ds-stats-table table, .ds-campaign-chart table { width: 100%; min-width: 1120px; border-collapse: collapse; }
.ds-stats-table th, .ds-stats-table td, .ds-campaign-chart th, .ds-campaign-chart td { padding: var(--ds-space-3); border-bottom: 1px solid var(--ds-line); text-align: left; vertical-align: middle; }
.ds-stats-table th, .ds-campaign-chart th { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-weight: 500; }
.ds-stats-table .ds-num, .ds-campaign-chart .ds-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ds-stats-row--warn { box-shadow: inset 3px 0 var(--ds-warn); }
.ds-stats-row--bad { box-shadow: inset 3px 0 var(--ds-danger); }
.ds-stats-row__name { display: block; color: var(--ds-ink); font-weight: 600; text-decoration: none; }
.ds-stats-row__lead { display: flex; align-items: flex-start; gap: var(--ds-space-2); }
.ds-stats-row__status-stack { display: flex; align-items: flex-start; flex-direction: column; gap: var(--ds-space-1); margin-top: var(--ds-space-1); }
.ds-stats-row__status-stack .ds-contract-status { margin: 0; }
.ds-stats-row__name:hover { color: var(--ds-accent); }
.ds-stats-row__meta, .ds-stats-row__delta, .ds-stats-row__muted, .ds-money span { display: block; margin-top: 2px; color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
/* «Что делать» в отказавшей строке таблицы подтверждения — обычным цветом и
   выше машинного кода: решение принимают по нему (заказ владельца 08.09). */
.ds-stats-row__next { display: block; margin-top: 4px; font-size: var(--ds-fs-caption); }
.ds-stats-row__details { margin-top: 4px; }
.ds-stats-row__details > summary { cursor: pointer; color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
/* Разбивка по видам кампаний: одна дополнительная строка под именем и ни
   пикселем больше. Виды разделены промежутком, а не точкой, — точка внутри
   вида уже значит «кампаний · заявок». */
.ds-stats-row__models { display: flex; flex-wrap: nowrap; gap: 0 var(--ds-space-2); margin-top: 2px; color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-stats-row__model { white-space: nowrap; }
.ds-stats-sort__link { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--ds-line); }
.ds-stats-sort__link:hover { color: var(--ds-accent); border-bottom-color: currentColor; }
.ds-stats-sort--active .ds-stats-sort__link { color: var(--ds-ink); font-weight: 700; border-bottom-color: currentColor; }
.ds-stats-sort__arrow { font-weight: 700; }
.ds-stats-row__tasks { color: var(--ds-danger); }
.ds-stats-row__actions { display: flex; align-items: flex-end; gap: var(--ds-space-1); flex-direction: column; }
.ds-stats-row__toggle { display: none; font-size: var(--ds-fs-label); }
.js .ds-stats-row__toggle[data-stats-nested-ready] { display: inline-flex; }
.ds-stats-row__expand[hidden], .ds-stats-disclosure__content[hidden] { display: none; }
.ds-stats-table__empty { padding: var(--ds-space-5); color: var(--ds-ink-2); text-align: center; }
.ds-stats-row__expand td { padding: var(--ds-space-3) var(--ds-space-4); background: var(--ds-surface-2); }
.ds-stats-nested { margin: 0 0 var(--ds-space-3); }
.ds-stats-nested:last-child { margin-bottom: 0; }
.ds-stats-nested__title { margin: var(--ds-space-2) 0; color: var(--ds-ink); font-size: var(--ds-fs-caption); font-weight: 600; }
.ds-stats-nested__scroll { overflow-x: auto; border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-stats-nested__table { width: 100%; min-width: 1040px; border-collapse: collapse; }
.ds-stats-row__expand .ds-stats-nested__table th,
.ds-stats-row__expand .ds-stats-nested__table td { padding: var(--ds-space-2); background: var(--ds-surface); font-size: var(--ds-fs-caption); }
.ds-stats-row__expand .ds-stats-nested__table tbody tr:last-child td { border-bottom: 0; }
.ds-signal-list, .ds-model-split, .ds-balance-card, .ds-campaign-chart, .ds-campaign-details > section { margin-bottom: var(--ds-space-5); }
.ds-signal-list__items { display: grid; gap: var(--ds-space-2); margin: 0; padding: 0; list-style: none; }
.ds-signal-list__item { display: flex; justify-content: space-between; gap: var(--ds-space-3); padding: var(--ds-space-3); border-radius: var(--ds-radius-s); background: var(--ds-warn-bg); }
.ds-signal-list__item--bad { border-left: 3px solid var(--ds-danger); background: var(--ds-danger-bg); }
.ds-signal-list__item strong { color: var(--ds-danger); }
.ds-signal-list__empty, .ds-model-split__note { color: var(--ds-ink-2); }
.ds-model-split__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ds-space-3); }
.ds-model-split__item { padding: var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); }
.ds-model-split__item span { display: block; margin-top: var(--ds-space-1); color: var(--ds-ink-2); }
.ds-stats-disclosure__bar { display: flex; align-items: baseline; justify-content: space-between; gap: var(--ds-space-2) var(--ds-space-4); min-width: 0; }
.ds-stats-disclosure__summary { min-width: 0; margin: 0; color: var(--ds-ink); font-size: var(--ds-fs-body); font-weight: 600; line-height: 1.45; overflow-wrap: anywhere; }
.ds-stats-disclosure__bar .ds-stats-row__toggle { flex: 0 0 auto; min-height: 44px; }
.ds-stats-disclosure__content { margin-top: var(--ds-space-3); }
/* Подсказка «не вмешиваться»: спокойный тон, не тревожный — это не проблема,
   а причина не трогать объект. */
.ds-quiet-note { display: inline-flex; align-items: baseline; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-3); padding: var(--ds-space-2) var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface); color: var(--ds-ink-2); }
.ds-quiet-note--inline { margin: 0; padding: 0; border: 0; background: none; font-size: var(--ds-fs-small); }
.ds-collection-gaps__list { display: grid; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-3); padding: 0; list-style: none; }
.ds-collection-gaps__item { display: grid; grid-template-columns: minmax(9rem, auto) minmax(0, 1fr) auto; align-items: baseline; gap: var(--ds-space-2) var(--ds-space-3); padding: var(--ds-space-2) var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-collection-gaps__name { font-weight: 600; overflow-wrap: anywhere; }
.ds-collection-gaps__more { margin: 0 0 var(--ds-space-3); color: var(--ds-ink-2); }
.ds-collection-gaps__refill, .ds-collection-gaps__action { margin: 0; }
@media (max-width: 720px) { .ds-collection-gaps__item { grid-template-columns: minmax(0, 1fr); } }
.ds-reconciliation__list { display: grid; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-3); padding: 0; list-style: none; }
.ds-reconciliation__item { display: grid; grid-template-columns: minmax(13rem, 1fr) repeat(3, auto); align-items: center; gap: var(--ds-space-2) var(--ds-space-4); padding: var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-reconciliation__item--unknown { border-color: var(--ds-danger-line); background: var(--ds-danger-bg); }
.ds-reconciliation__warning { color: var(--ds-danger); font-weight: 600; grid-column: 1 / -1; }
.ds-reconciliation__empty { color: var(--ds-ink-2); }
.ds-reconciliation__excluded { margin: 0 0 var(--ds-space-3); }
.ds-reconciliation__excluded h3 { margin: 0 0 var(--ds-space-2); font-size: var(--ds-fs-body); }
.ds-reconciliation__excluded ul { display: grid; gap: var(--ds-space-1); margin: 0; padding: 0; list-style: none; }
.ds-reconciliation__excluded li { display: grid; grid-template-columns: 7rem minmax(12rem, 1fr) auto; gap: var(--ds-space-2); align-items: baseline; padding: var(--ds-space-2); border-bottom: 1px solid var(--ds-line); }
.ds-balance-card { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--ds-space-3); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); }
.ds-balance-card strong { font-size: 24px; }
.ds-balance-card--bad { border-color: var(--ds-danger); }
.ds-balance-card--warn { border-color: var(--ds-warn); }
.ds-campaign-chart__spark { margin-bottom: var(--ds-space-3); }
/* Карточная версия — тот же viewBox, увеличенный целиком. ВЫСОТА НЕ ЗАДАЁТСЯ:
   у SVG с viewBox она считается от ширины по его же пропорции, и картинка
   заполняет клетку без остатка при ЛЮБОЙ ширине. Заданная высота 118 при
   пропорции 146×45 оставляла по 88 px пустых полей с каждой стороны — SVG
   вписывается меньшим из двух отношений, и лишняя высота просто не давала
   картинке растянуться. Кегль подписи задан в единицах viewBox, иначе
   почти четырёхкратное увеличение даёт числа в 42 px. */
.ds-campaign-chart__spark .ds-spark { width: 100%; max-width: 560px; height: auto; }
/* Крупный график кампании тянет ту же картинку в 3,8 раза: кегль, обводка и
   радиус пересчитаны под этот масштаб, иначе подпись закрывала бы сам график.
   Слово пустого окна — тоже: десять единиц здесь дают 38 px. */
.ds-campaign-chart__spark .ds-spark__value { font-size: 3px; stroke-width: 0.7px; }
.ds-campaign-chart__spark .ds-spark__dot { r: 1px; }
.ds-campaign-chart__spark .ds-spark__word { font-size: 3.5px; }
.ds-campaign-chart__spark .ds-spark__word--narrow { font-size: 3px; }
.ds-campaign-chart__preliminary { color: var(--ds-ink-2); background: var(--ds-surface-2); }
.ds-campaign-chart__preliminary td:first-child span { display: block; font-size: var(--ds-fs-caption); }
.ds-campaign-group > h3 { margin: 0 0 var(--ds-space-2); color: var(--ds-ink-2); font-size: var(--ds-fs-body); }
.ds-archive { margin: var(--ds-space-4) 0 var(--ds-space-5); padding: var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface-2); }
.ds-archive > summary { min-height: 44px; cursor: pointer; font-weight: 600; }
.ds-campaign-details { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ds-space-4); }
.ds-campaign-details > section { padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); }
.ds-project-focus { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--ds-space-3); margin-bottom: var(--ds-space-5); }
.ds-project-focus article { padding: var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-project-focus span { display: block; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-project-focus strong { display: block; margin-top: var(--ds-space-1); font-size: var(--ds-fs-section); }
.ds-project-focus small { display: block; margin-top: 2px; color: var(--ds-ink-3); font-size: var(--ds-fs-label); }
.ds-cabinet-journal { margin: var(--ds-space-5) 0; padding: var(--ds-space-4); border-top: 1px solid var(--ds-line); }
.ds-contract-status { display: inline-flex; align-items: center; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-contract-status strong { color: var(--ds-ink); }
.ds-contract-status--ok, .ds-contract-status--ok strong { color: var(--ds-accent); }
.ds-contract-status--warn, .ds-contract-status--warn strong { color: var(--ds-warn); }
.ds-contract-status--bad, .ds-contract-status--bad strong { color: var(--ds-danger); }
.ds-contract-list { display: grid; gap: var(--ds-space-2); margin: 0; }
.ds-contract-list div { display: grid; grid-template-columns: minmax(140px, 1fr) minmax(120px, 1fr); gap: var(--ds-space-3); padding-bottom: var(--ds-space-2); border-bottom: 1px solid var(--ds-line); }
.ds-contract-list dt { color: var(--ds-ink-2); }
.ds-contract-list dd { margin: 0; font-variant-numeric: tabular-nums; }
.ds-contract-goals { display: grid; gap: var(--ds-space-2); margin: 0; padding: 0; list-style: none; }
.ds-contract-goals li { display: flex; justify-content: space-between; gap: var(--ds-space-3); padding-bottom: var(--ds-space-2); border-bottom: 1px solid var(--ds-line); }
.ds-contract-goals span, .ds-contract-card__missing { color: var(--ds-ink-2); }
.ds-contract-card--wide { grid-column: 1 / -1; }
.ds-contract-table { overflow-x: auto; }
.ds-contract-table table { width: 100%; min-width: 680px; border-collapse: collapse; }
.ds-contract-table th, .ds-contract-table td { padding: var(--ds-space-2); border-bottom: 1px solid var(--ds-line); text-align: left; }
.ds-contract-table th { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-weight: 500; }
.ds-contract-table .ds-num { text-align: right; font-variant-numeric: tabular-nums; }
.ds-weekly-slices { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ds-space-4); margin-bottom: var(--ds-space-5); }
.ds-weekly-slice { min-width: 0; padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-weekly-slice__head { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--ds-space-2); margin-bottom: var(--ds-space-3); }
.ds-weekly-slice__head .ds-section-title { margin: 0 auto 0 0; }
.ds-weekly-slice__head > span { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-weekly-slice__scroll { overflow-x: auto; }
.ds-weekly-slice table { width: 100%; min-width: 620px; border-collapse: collapse; }
.ds-weekly-slice caption { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.ds-weekly-slice th, .ds-weekly-slice td { padding: var(--ds-space-2); border-bottom: 1px solid var(--ds-line); text-align: left; vertical-align: top; }
.ds-weekly-slice th { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-weight: 500; }
.ds-weekly-slice .ds-num { text-align: right; font-variant-numeric: tabular-nums; }
.ds-weekly-slice__qualifier { color: var(--ds-warn); font-size: var(--ds-fs-caption); }
.ds-weekly-slice__warning { margin: 0 0 var(--ds-space-3); color: var(--ds-warn); font-size: var(--ds-fs-caption); }
.ds-weekly-slice__empty { margin: 0; color: var(--ds-ink-2); }
.ds-tracker-projection { margin-bottom: var(--ds-space-5); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-tracker-projection--embedded { margin: 0; padding: 0; border: 0; }
.ds-tracker-projection__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--ds-space-3); margin-bottom: var(--ds-space-3); }
.ds-tracker-projection__head .ds-section-title { margin: 0; }
.ds-tracker-projection__head > span { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; }
.ds-tracker-projection__traffic { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ds-space-2); margin: 0; }
.ds-tracker-projection__traffic div { display: flex; justify-content: space-between; gap: var(--ds-space-2); padding: var(--ds-space-2); border-bottom: 1px solid var(--ds-line); }
.ds-tracker-projection__traffic dt { color: var(--ds-ink-2); }
.ds-tracker-projection__traffic dd { margin: 0; font-variant-numeric: tabular-nums; }
.ds-tracker-projection__binding { margin-top: var(--ds-space-3); }
.ds-tracker-projection__binding h3 { margin: 0 0 var(--ds-space-2); font-size: var(--ds-fs-body); }
.ds-tracker-projection__binding ul { display: flex; flex-wrap: wrap; gap: var(--ds-space-2) var(--ds-space-4); margin: 0; padding: 0; list-style: none; }
.ds-tracker-projection__binding li { display: flex; gap: var(--ds-space-2); }
.ds-tracker-projection__binding span { color: var(--ds-ink-2); }
.ds-tracker-projection__unassigned { margin: var(--ds-space-3) 0 0; color: var(--ds-warn); }
.ds-tracker-projection__empty { margin: 0; color: var(--ds-ink-2); }
.ds-lead-settings { margin-bottom: var(--ds-space-5); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-lead-settings__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ds-space-4); margin-bottom: var(--ds-space-3); }
.ds-lead-settings__head p { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); }
.ds-lead-settings__head > span { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); white-space: nowrap; }
.ds-lead-settings__goals { display: grid; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-4); padding: 0; border: 0; }
.ds-lead-settings__goal { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; align-items: center; min-height: 56px; padding: var(--ds-space-2); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); cursor: pointer; }
.ds-lead-settings__goal input { width: 20px; height: 20px; margin: 0 auto; accent-color: var(--ds-accent); }
.ds-lead-settings__identity, .ds-lead-settings__counts { display: flex; flex-direction: column; gap: 2px; }
.ds-lead-settings__identity span, .ds-lead-settings__counts, .ds-lead-settings__counts small { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-lead-settings__counts { text-align: right; font-variant-numeric: tabular-nums; }
.ds-lead-settings__goal--test { background: var(--ds-surface-2); cursor: not-allowed; }
.ds-lead-settings__empty { margin: 0; color: var(--ds-ink-2); }

@media (max-width: 1100px) {
  .ds-metric-grid, .ds-project-focus { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 720px) {
  .ds-stats-head { align-items: stretch; flex-direction: column; }
  .ds-stats-head__actions .ds-button { width: 100%; }
  .ds-weekly-refill { display: flex; align-items: stretch; flex-direction: column; }
  .ds-metric-grid, .ds-project-focus, .ds-model-split__grid, .ds-campaign-details, .ds-weekly-slices, .ds-tracker-projection__traffic { grid-template-columns: 1fr; }
  .ds-tracker-projection__head { align-items: flex-start; flex-direction: column; }
  .ds-lead-settings__head { flex-direction: column; }
  .ds-stats-disclosure__bar { align-items: flex-start; flex-direction: column; }
  .ds-reconciliation__item { grid-template-columns: 1fr; }
  .ds-reconciliation__excluded li { grid-template-columns: 1fr; }
  .ds-lead-settings__goal { grid-template-columns: 44px minmax(0, 1fr); }
  .ds-lead-settings__counts { grid-column: 2; text-align: left; }
  .ds-contract-card--wide { grid-column: auto; }
  .ds-contract-list div { grid-template-columns: 1fr; gap: var(--ds-space-1); }
  .ds-stats-filters { align-items: stretch; flex-direction: column; }
  .ds-stats-filters > * { width: 100%; }
}

/* Список задач (B1): счётчик «требуют решения» раскрывается в перечень. */
.ds-tasklist { display: grid; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-5); padding: 0; list-style: none; }
.ds-tasklist__item { padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-line); border-left: 3px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-tasklist__item--bad { border-left-color: var(--ds-danger); background: var(--ds-danger-bg); }
.ds-tasklist__item--warn { border-left-color: var(--ds-warn); background: var(--ds-warn-bg); }
.ds-tasklist__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); }
.ds-tasklist__label { font-size: var(--ds-fs-section); font-weight: 600; }
.ds-tasklist__redline { font-size: var(--ds-fs-label); text-transform: uppercase; letter-spacing: .04em; color: var(--ds-danger); }
.ds-tasklist__status { margin-left: auto; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-tasklist__status--pending { color: var(--ds-warn); }
.ds-tasklist__object { margin: var(--ds-space-2) 0 0; font-size: var(--ds-fs-body); }
.ds-tasklist__kind { font-size: var(--ds-fs-label); text-transform: uppercase; letter-spacing: .04em; color: var(--ds-ink-3); }
.ds-tasklist__id, .ds-tasklist__path { color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-tasklist__evidence { margin: var(--ds-space-1) 0 0; font-variant-numeric: tabular-nums; }
.ds-tasklist__meta { margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-signal-list__bar { display: flex; align-items: baseline; justify-content: space-between; gap: var(--ds-space-3); }
.ds-signal-list__all { font-size: var(--ds-fs-caption); }
.ds-stats-row__tasks-link { text-decoration: none; }
.ds-stats-row__tasks-link:hover .ds-stats-row__tasks { text-decoration: underline; }

/* --- динамика (B6) --------------------------------------------------------
   Цвета, размеры и отступы — из утверждённого владельцем макета
   (scratchpad/design/Main.dc.html). Ось одна на все ряды, подписаны все три
   ряда, полая точка с серым числом = «дозревают». */

.ds-dynamics { margin: var(--ds-space-5) 0; }
.ds-dynamics__bar { display: flex; align-items: baseline; justify-content: space-between; gap: var(--ds-space-2) var(--ds-space-4); flex-wrap: wrap; }
.ds-dynamics__totals { margin: 0; color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-dynamics__totals strong { color: var(--ds-ink); }
.ds-dynamics__totals .ds-dynamics__maturing { color: var(--ds-ink-2); font-weight: 600; }

.ds-dynamics__cuts { display: flex; align-items: center; gap: var(--ds-space-2); flex-wrap: wrap; margin: var(--ds-space-3) 0; }
.ds-dynamics__cuts-label { font-size: var(--ds-fs-label); color: var(--ds-ink-3); text-transform: uppercase; letter-spacing: .04em; }
.ds-dynamics__chips { display: flex; gap: var(--ds-space-2); flex-wrap: wrap; }
.ds-dynamics__chip { display: inline-flex; align-items: center; gap: 6px; height: var(--ds-chip-h); padding: 0 14px; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill); background: var(--ds-surface); color: var(--ds-ink); font-size: var(--ds-fs-caption); text-decoration: none; white-space: nowrap; }
.ds-dynamics__chip:hover { border-color: var(--ds-ink-3); }
.ds-dynamics__chip-count { color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-dynamics__chip--active { background: var(--ds-ink); border-color: var(--ds-ink); color: var(--ds-surface); }
.ds-dynamics__chip--active .ds-dynamics__chip-count { color: var(--ds-line-2); }
/* Оговорка «цена уточнится вниз» у таблетки разреза: та же короткая форма,
   что в тесной ячейке цены в таблицах. Цена чипа считается по ПОЛНОМУ окну,
   и без этой строки таблетка печатала бы завышенное число молча. */
.ds-dynamics__chip-note { color: var(--ds-ink-3); font-size: var(--ds-fs-label); }
.ds-dynamics__chip--active .ds-dynamics__chip-note { color: var(--ds-line-2); }
.ds-dynamics__toggle { display: inline-flex; align-items: center; gap: 6px; height: var(--ds-chip-h); padding: 0 var(--ds-space-2); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); text-decoration: none; border-radius: var(--ds-radius-s); white-space: nowrap; }
.ds-dynamics__toggle:hover { color: var(--ds-ink); background: var(--ds-surface-2); }
.ds-dynamics__toggle--on { color: var(--ds-ink); }
.ds-dynamics__box { width: 14px; height: 14px; border: 1px solid var(--ds-line-2); border-radius: 4px; background: var(--ds-surface); }
.ds-dynamics__toggle--on .ds-dynamics__box { border-color: var(--ds-accent); background: var(--ds-accent); box-shadow: inset 0 0 0 2px var(--ds-surface); }

.ds-dynamics__card { background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); padding: var(--ds-space-5) var(--ds-space-5) var(--ds-space-4); margin-bottom: var(--ds-space-3); }
.ds-dynamics__scroll { overflow-x: auto; }
.ds-dynamics__canvas { min-width: 680px; }
.ds-dynamics__chart { display: block; width: 100%; height: 264px; }
.ds-dynamics__spend-wrap { margin-top: var(--ds-space-3); padding-top: var(--ds-space-3); border-top: 1px solid #f0efe9; }
.ds-dynamics__spend-title { display: block; margin-bottom: 2px; font-size: var(--ds-fs-label); color: var(--ds-ink-3); text-transform: uppercase; letter-spacing: .04em; }
.ds-dynamics__spend { display: block; width: 100%; height: 96px; }

.ds-dynamics__gap { fill: var(--ds-surface-2); }
.ds-dynamics__bar-all, .ds-dynamics__bar-search { fill: #eceae2; }
.ds-dynamics__bar-network { fill: #f5f4ef; }
/* Подпись столбца стоит внутри него, и линия переходов проходит сквозь неё:
   без гало «515» и линия сливались в кляксу. */
.ds-dynamics__bar-value { fill: var(--ds-ink-3); font-size: 13px; font-variant-numeric: tabular-nums; stroke: var(--ds-surface); stroke-width: 3px; stroke-linejoin: round; }
.ds-dynamics__bar-value--inside { fill: var(--ds-ink-2); }
.ds-dynamics__line { fill: none; stroke: var(--ds-ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.ds-dynamics__line--gap { stroke-dasharray: 4 3; opacity: .5; }
.ds-dynamics__click-value { fill: var(--ds-ink-2); font-size: 12px; font-variant-numeric: tabular-nums; stroke: var(--ds-surface); stroke-width: 3px; stroke-linejoin: round; }
.ds-dynamics__dot { fill: var(--ds-accent); stroke: var(--ds-surface); stroke-width: 2; vector-effect: non-scaling-stroke; }
.ds-dynamics__dot--zero { fill: var(--ds-surface); stroke: var(--ds-danger); }
/* Полая серая точка — «ещё дозревают»; это НЕ ноль, и путать их нельзя. */
.ds-dynamics__dot--raw { fill: var(--ds-surface); stroke: var(--ds-ink-3); }
.ds-dynamics__dot-value { fill: var(--ds-accent); font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; stroke: var(--ds-surface); stroke-width: 3px; stroke-linejoin: round; }
.ds-dynamics__dot-value--raw { fill: var(--ds-ink-2); }
.ds-dynamics__mark { stroke: var(--ds-ink-3); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.ds-dynamics__mark--heavy { stroke: var(--ds-danger); stroke-width: 2; stroke-dasharray: none; opacity: .7; }
.ds-dynamics__hit { fill: transparent; }
.ds-dynamics__hit:hover, .ds-dynamics__hit:focus { fill: var(--ds-ink); fill-opacity: .04; outline: none; }
.ds-dynamics__hit:focus-visible { stroke: var(--ds-accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
.ds-dynamics__spend-bar { fill: #b9b9b1; }
.ds-dynamics__spend-bar--gap { fill: var(--ds-line-2); }
.ds-dynamics__spend-value { fill: var(--ds-ink-2); font-size: 13px; font-variant-numeric: tabular-nums; }

.ds-dynamics__dates { display: flex; margin: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; }
.ds-dynamics__date { flex: 1 1 0; min-width: 0; text-align: center; white-space: nowrap; }

.ds-dynamics__pointer, .ds-dynamics__empty { margin: var(--ds-space-3) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-dynamics__legend { display: flex; align-items: center; gap: var(--ds-space-2) var(--ds-space-5); flex-wrap: wrap; margin: var(--ds-space-3) 0 0; padding-top: var(--ds-space-3); border-top: 1px solid #f0efe9; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-dynamics__key { display: inline-block; width: 14px; height: 10px; margin-right: 2px; border-radius: 2px; vertical-align: middle; }
.ds-dynamics__key--bars { background: #eceae2; }
.ds-dynamics__key--line { height: 2px; background: var(--ds-ink); }
.ds-dynamics__key--dot { width: 12px; height: 12px; border-radius: 50%; background: var(--ds-accent); }
.ds-dynamics__key--raw { width: 12px; height: 12px; border-radius: 50%; background: var(--ds-surface); border: 2px solid var(--ds-ink-3); }
.ds-dynamics__key--spend { background: #b9b9b1; }

.ds-dynamics__marks { display: grid; gap: var(--ds-space-1); margin: 0 0 var(--ds-space-3); padding: 0; list-style: none; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-dynamics__mark-item strong { color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.ds-dynamics__mark-item--heavy, .ds-dynamics__mark-item--heavy strong { color: var(--ds-danger); }

.ds-dynamics__table-title { margin: 0 0 var(--ds-space-3); font-size: var(--ds-fs-body); font-weight: 600; }
.ds-dynamics__table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.ds-dynamics__table th, .ds-dynamics__table td { padding: 9px var(--ds-space-2) 9px 0; border-bottom: 1px solid #f0efe9; text-align: left; }
.ds-dynamics__table thead th { color: var(--ds-ink-2); font-weight: 400; font-size: var(--ds-fs-label); text-transform: uppercase; letter-spacing: .04em; border-bottom: 1px solid var(--ds-line); }
.ds-dynamics__table tbody th { font-weight: 600; }
.ds-dynamics__cell--raw { color: var(--ds-ink-3); }
/* Собранное за свежий день — числом с пометкой. «·» на дне, за который в базе
   лежат 45 заявок, читался как «заявок нет»: собранный факт не имеет права
   выглядеть как отсутствие факта. */
.ds-dynamics__cell-note { color: var(--ds-ink-3); font-size: var(--ds-fs-label); white-space: nowrap; }
.ds-dynamics__row--gap { background: var(--ds-surface-2); }
.ds-dynamics__note { color: var(--ds-ink-3); font-size: var(--ds-fs-caption); }

@media (max-width: 720px) {
  .ds-dynamics__card { padding: var(--ds-space-3); }
}

/* Колонка периода в таблице — мини-версия динамики (макет StrokiTablitsy):
   расход столбиками фоном, заявки точками, ЧИСЛО над каждой ненулевой.
   Кабинет без заявок читается мгновенно: столбики идут, точек нет. */
.ds-spark { display: block; width: 100%; height: 45px; }
/* Нулевая линия — ПОЛ картинки, и она есть всегда. Без неё столбики росли из
   пустоты, точки заявок висели в воздухе, а клетка без расхода выглядела так,
   будто вёрстка не сработала. Линия делает пустое НАБЛЮДЕНИЕМ: ось есть, на
   ней ноль. Цвет из утверждённого макета design-otbor. */
.ds-spark__baseline { stroke: #e6e6e1; stroke-width: 1; }
.ds-spark__bar { fill: #eceae2; }
/* Прошлое окно на картинке из двух окон: те же столбики бледнее, а граница
   говорит, где кончилось «было» и началось «стало». Обрыв заявок владелец
   должен видеть глазами, а не вычислять из двух чисел. */
.ds-spark__bar--past { fill: #f4f3ee; }
/* ГРАНИЦА ОКОН — СПЛОШНАЯ вертикаль почти на всю высоту (макет: «граница окон
   — сплошная вертикаль на всю высоту, не пунктир»). Пунктир в узкой колонке
   читался как случайная рябь, а не как граница, и правилами макета прямо
   запрещён. */
.ds-spark__divider { stroke: #d9d9d3; stroke-width: 1.4; }
.ds-spark__dot { fill: var(--ds-accent); stroke: #fff; stroke-width: 1; }
/* Незрелый день — КОНТУРНАЯ точка: собранное число есть, но итогом окна оно
   ещё не стало. Заливка фоном страницы, а не бледным цветом заявки:
   закрашенная бледная точка читалась как «заявка послабее», а это не
   «слабее», а «ещё не окончательно». */
.ds-spark__dot--raw { fill: #fbfbf9; stroke: #a1a5ab; stroke-width: 1.3; }
/* ЧИСЛО ЗАЯВОК — 11 пунктов и жирное. Девять пунктов владелец назвал
   нечитаемыми дословно; картинка рисуется один к одному, поэтому кегль в
   макете и есть кегль на экране. Белая обводка идёт ПОД заливкой
   (paint-order): подпись то и дело приходится на серый столбик соседнего дня,
   и без подложки цифра тонула в нём. */
.ds-spark__value { fill: var(--ds-accent); font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--ds-surface); stroke-width: 2.6px; stroke-linejoin: round; }
/* Дозревающий день говорит своё число ПРИГЛУШЁННО — тоном контура своей
   точки, не зелёным цветом заявки. Так на одной картинке различимы
   окончательное число и то, которое ещё дособерётся. Тон взят темнее самого
   контура (#a1a5ab): контур читается формой, а цифра — только контрастом, и
   на цвете контура 11-й кегль давал 2,3:1 вместо требуемых 4,5:1. */
.ds-spark__value--raw { fill: #6b7079; }
/* СЛОВО ВМЕСТО ПУСТОТЫ. Тревожным цветом — только «заявок нет» в ТЕКУЩЕМ окне
   картинки из двух: это и есть обрыв, ради которого экран заведён. Всё
   остальное — спокойная серая констатация. */
.ds-spark__word { fill: #a1a5ab; font-size: 10px; }
/* Картинка из двух окон отдаёт слову половину ширины, а «показов не было» в
   неё не помещается: два таких слова сходились посередине в одну строку без
   пробела. Ужимается только этот случай — одному окну места хватает. */
.ds-spark__word--narrow { font-size: 8.5px; }
.ds-spark__word--alarm { fill: #b4231a; font-weight: 600; }
.ds-stats-row__spark { width: 300px; min-width: 200px; }
.ds-stats-row__spark-head { width: 300px; }
/* Колонке итога нужна своя ширина: nowrap-текст в узкой ячейке вылезал
   влево и ложился прямо на график. */
.ds-stats-row__sum-head { width: 120px; }
.ds-stats-row__spark-sum { min-width: 120px; white-space: nowrap; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-stats-row__spark-sum--burning { color: var(--ds-danger); }

/* Спарклайн в строке обязан НАЗЫВАТЬ значение: «точки без цифр» владелец
   назвал неинформативными, а элемент, который ничего не сообщает, — шум. */
.ds-spark-cell { display: inline-flex; align-items: center; gap: var(--ds-space-2); }
.ds-spark-cell__value { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* --- Витрина действий (A6) ------------------------------------------------
   Предложение обязано читаться без перехода: имя типа, объект с номером,
   деньги машинным числом под ним, доказательство цифрами, дата. Красная
   линия отличается рамкой и словами, а не только цветом. */
.ds-wave { margin: 0 0 var(--ds-space-5); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-wave__head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--ds-space-3); }
.ds-wave__meta { margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-wave__action { display: flex; align-items: center; }
.ds-wave__items { margin: var(--ds-space-3) 0 0; padding-left: var(--ds-space-4); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-wave__money { font-variant-numeric: tabular-nums; }

.ds-actionlist { display: grid; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-5); padding: 0; list-style: none; }
.ds-actionlist__item { padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-line); border-left: 3px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-actionlist__item--red-line { border-left-color: var(--ds-danger); background: var(--ds-danger-bg); }
.ds-actionlist__item--stale { border-left-color: var(--ds-warn); background: var(--ds-warn-bg); }
.ds-actionlist__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); }
.ds-actionlist__title { font-size: var(--ds-fs-section); font-weight: 600; }
.ds-actionlist__redline, .ds-actionlist__unknown { font-size: var(--ds-fs-label); text-transform: uppercase; letter-spacing: .04em; color: var(--ds-danger); }
.ds-actionlist__status { margin-left: auto; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-actionlist__status--pending { color: var(--ds-warn); }
.ds-actionlist__object { margin: var(--ds-space-2) 0 0; font-size: var(--ds-fs-body); }
.ds-actionlist__kind { font-size: var(--ds-fs-label); text-transform: uppercase; letter-spacing: .04em; color: var(--ds-ink-3); }
.ds-actionlist__value { color: var(--ds-ink-2); }
.ds-actionlist__essence { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); }
.ds-actionlist__money { margin: var(--ds-space-2) 0 0; font-variant-numeric: tabular-nums; }
.ds-actionlist__money strong { font-size: var(--ds-fs-stat-cabinet); }
.ds-actionlist__money span { margin-left: var(--ds-space-2); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-actionlist__evidence { margin: var(--ds-space-1) 0 0; font-variant-numeric: tabular-nums; }
.ds-actionlist__meta { margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }

.ds-actioncard { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--ds-space-3); margin: 0 0 var(--ds-space-5); }
.ds-actioncard__block { padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface); }
.ds-actioncard__essence { margin: var(--ds-space-2) 0 0; font-size: var(--ds-fs-section); }
.ds-actioncard__money { margin: var(--ds-space-3) 0 0; font-variant-numeric: tabular-nums; }
.ds-actioncard__money strong { font-size: var(--ds-fs-stat-card); }
.ds-actioncard__money span { margin-left: var(--ds-space-2); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
/* ВТОРОЕ ЧИСЛО — НЕТТО. Отдельной строкой под главным и мельче его: по нему
   сверяются с отчётом кабинета Директа и с трекером, но читают его реже. */
.ds-actioncard__money-net { display: block; margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
/* Подпись ставки у суммы в списке волны — тише самой суммы. */
.ds-wave__money-caption { font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
/* Подпись КРАСНОЙ ЗОНЫ: какое именно число подписывают. */
.ds-edit-row__vat, .ds-contract-card__vat { margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-actioncard__object, .ds-actioncard__evidence, .ds-actioncard__mode { margin: var(--ds-space-2) 0 0; }
.ds-actioncard__path { margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-actioncard__subtitle { margin: var(--ds-space-3) 0 0; font-size: var(--ds-fs-body); }
.ds-actioncard__facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--ds-space-1) var(--ds-space-3); margin: var(--ds-space-2) 0 0; font-size: var(--ds-fs-caption); }
.ds-actioncard__facts dt { color: var(--ds-ink-2); }
.ds-actioncard__facts dd { margin: 0; font-variant-numeric: tabular-nums; }
.ds-actioncard__risks { margin: var(--ds-space-2) 0 0; padding-left: var(--ds-space-4); }
.ds-actioncard__redline { margin: var(--ds-space-2) 0 0; color: var(--ds-danger); font-weight: 600; }
.ds-actioncard__note { margin: var(--ds-space-2) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-actioncard__decision { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-3); margin: 0 0 var(--ds-space-6); }
.ds-actioncard__decision .ds-actioncard__note { flex-basis: 100%; margin: 0; }
.ds-actioncard__ack { display: block; margin: 0 0 var(--ds-space-2); font-size: var(--ds-fs-caption); color: var(--ds-warn); }
/* Закрытие упавшего без повтора: причина — поле в строку с кнопкой, чтобы
   жест читался одним куском («почему» → «закрыть»), а не распадался на
   форму сверху и кнопку где-то ниже. */
.ds-actioncard__dismiss { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--ds-space-2); flex-basis: 100%; }
.ds-actioncard__reason { display: flex; flex-direction: column; gap: var(--ds-space-1); flex: 1 1 22rem; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-actioncard__reason input { width: 100%; min-height: 38px; padding: 0 var(--ds-space-3); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); color: var(--ds-ink); font: inherit; font-size: 14px; }

/* --- второй замок: «снять защиту флагмана и остановить» (08.09) ---------
   Жест один, показов два. СО СКРИПТОМ виден только открывающий кнопку —
   форма живёт в окне (требование владельца: «всё через попапы»). БЕЗ СКРИПТА
   наоборот: окно <dialog> браузер не покажет, поэтому форма стоит прямо на
   карточке, а кнопка, которой нечего открывать, спрятана. Ни один из двух
   показов не отменяет жест целиком — это и есть смысл такой пары. */
.ds-flagship-stop { flex-basis: 100%; margin: 0 0 var(--ds-space-4); }
.ds-flagship-stop__open { display: none; }
html.js .ds-flagship-stop__open { display: inline-flex; }
html.js .ds-flagship-stop__inline { display: none; }
.ds-flagship-stop__decision { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-3); margin-top: var(--ds-space-4); }

/* --- прямое управление кампанией (docs/31 §Слой 1) ---------------------- */
/* Галочка строки — цель нажатия, а не значок. 18 пикселей и своя зона
   промаха: по 13-пиксельному квадрату в таблице на 15 строк не попадают. */
.ds-campaign-pick { width: 18px; height: 18px; margin-right: 8px; accent-color: var(--ds-accent); cursor: pointer; }
.ds-campaign-control { display: inline-flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.ds-campaign-control__why { color: var(--ds-ink-2); font-size: 12px; max-width: 22ch; }
.ds-campaign-batch { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 8px 0 12px; padding: 10px 12px; border: 1px solid var(--ds-line-2); border-radius: 10px; background: var(--ds-surface-2); }
.ds-campaign-batch .ds-button[disabled] { opacity: .5; cursor: default; }
/* Панель пакета ЛИПНЕТ под шапкой — но только там, где ей есть где липнуть.
   В кабинете 15 кампаний, галочки ставят по всей длине таблицы, а кнопка
   стояла наверху: за ней приходилось скроллить обратно. ``top`` равен высоте
   topbar (56px), ``z-index`` на единицу ниже его — навигацию панель не
   накрывает. На телефоне липкости НЕТ намеренно: четыре кнопки с длинными
   подписями занимают там треть экрана, и постоянно висящая панель мешала бы
   читать сами кампании. */
@media (min-width: 721px) {
  .ds-campaign-batch { position: sticky; top: 56px; z-index: 4; box-shadow: 0 1px 0 var(--ds-line); }
}
.ds-campaign-batch__count { color: var(--ds-ink-2); }
.ds-campaign-status { display: inline-block; font-size: 12px; text-decoration: none; }
.ds-campaign-status--ok { color: var(--ds-accent); }
.ds-campaign-status--warn { color: var(--ds-warn); }
.ds-campaign-status--bad { color: var(--ds-danger); }
.ds-campaign-status--muted { color: var(--ds-ink-2); }
.ds-campaign-direct { margin: 12px 0; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
/* СОСТОЯНИЕ КАМПАНИИ НА КАРТОЧКЕ: чип каталога и, если есть, накладка «по
   вашему действию» рядом с ним. Приписка накладки блочная (общий компонент
   ds/campaign_state_chips.html), поэтому строка встаёт своим абзацем и не
   ломает ряд кнопок карточки. */
.ds-campaign-state { margin: 0; display: flex; align-items: baseline; flex-wrap: wrap; gap: 0; }
.ds-campaign-confirm__decision { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; margin-top: 16px; }
.ds-campaign-confirm__decision form { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.ds-campaign-confirm__decision .ds-actioncard__note { flex-basis: 100%; margin: 0; }
.ds-campaign-confirm__decision .ds-actioncard__redline { flex-basis: 100%; margin: 0 0 4px; }
/* Пометка «флагман» у строки кампании: движок такую бережёт, и нажатие владельца
   снимает с неё защиту — слово обязано быть заметным, а не сноской. */
.ds-campaign-flagship { display: inline-block; margin-left: 6px; padding: 0 6px; border: 1px solid var(--ds-danger); border-radius: var(--ds-radius-s); color: var(--ds-danger); font-size: var(--ds-fs-label); font-weight: 600; text-transform: lowercase; white-space: nowrap; }
/* Утверждение про деньги: своя строка, свой вес — его нельзя проскочить взглядом. */
.ds-actioncard__redline--money { padding: var(--ds-space-2) var(--ds-space-3); border: 1px solid var(--ds-danger); border-radius: var(--ds-radius-s); }
.ds-campaign-confirm__ack { flex-basis: 100%; display: flex; gap: 10px; align-items: flex-start; margin: 0 0 4px; padding: var(--ds-space-2) var(--ds-space-3); border: 1px solid var(--ds-danger); border-radius: var(--ds-radius-s); font-weight: 600; color: var(--ds-danger); cursor: pointer; }
.ds-campaign-confirm__ack input { margin-top: 2px; width: 18px; height: 18px; accent-color: var(--ds-danger); flex: none; }

/* --- окно подтверждения над списком кампаний ---------------------------- */
.ds-modal { width: min(1000px, calc(100vw - 2 * var(--ds-space-4))); max-height: calc(100vh - 2 * var(--ds-space-5)); padding: 0; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius); background: var(--ds-surface); color: var(--ds-ink); box-shadow: var(--ds-shadow); }
.ds-modal::backdrop { background: rgba(22, 24, 28, .5); }
.ds-modal__frame { position: relative; max-height: calc(100vh - 2 * var(--ds-space-5)); overflow-y: auto; padding: var(--ds-space-5); }
.ds-modal__close { position: absolute; top: var(--ds-space-2); right: var(--ds-space-2); display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; padding: 0; border: 1px solid transparent; border-radius: var(--ds-radius-s); background: none; color: var(--ds-ink-2); font: inherit; font-size: var(--ds-fs-section); line-height: 1; cursor: pointer; }
.ds-modal__close:hover { color: var(--ds-ink); border-color: var(--ds-line-2); background: var(--ds-surface-2); }
.ds-modal__note { margin: 0 0 var(--ds-space-3); color: var(--ds-ink-2); }
.ds-modal .msg, .ds-modal .ds-stats-head { margin: 0 var(--ds-space-6) var(--ds-space-3) 0; }
.ds-modal .ds-stats-head__title { font-size: var(--ds-fs-section); }
.ds-modal .ds-stats-table table { min-width: 720px; }
html.ds-modal-open, html.ds-modal-open body { overflow: hidden; }
@media (max-width: 720px) {
  .ds-modal__frame { padding: var(--ds-space-4); }
}

/* --- журнал действий (docs/31 §5, docs/32 §7) --------------------------- */
.ds-journal-block { margin: var(--ds-space-5) 0; }
.ds-journal-block__note { margin: 0 0 var(--ds-space-3); font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-journal { display: grid; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-4); padding: 0; list-style: none; }
.ds-journal__item { padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-line); border-left: 3px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-journal__item--ok { border-left-color: var(--ds-accent); }
.ds-journal__item--warn { border-left-color: var(--ds-warn); }
.ds-journal__item--bad { border-left-color: var(--ds-danger); background: var(--ds-danger-bg); }
.ds-journal__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); margin: 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-journal__when { font-variant-numeric: tabular-nums; }
.ds-journal__who { color: var(--ds-ink); }
.ds-journal__transition { margin-left: auto; color: var(--ds-ink); }
/* Прочерк вместо «кто» или «когда» — не мелочь оформления: журнал без них
   порча, и красным он виден в ленте, а не только при вычитывании строки. */
.ds-journal__gap { color: var(--ds-danger); }
.ds-journal__what { margin: var(--ds-space-1) 0 0; }
.ds-journal__title { font-weight: 600; }
.ds-journal__object { margin-left: var(--ds-space-2); color: var(--ds-ink-2); }
.ds-journal__changes { display: flex; flex-wrap: wrap; gap: var(--ds-space-3); margin: var(--ds-space-1) 0 0; font-variant-numeric: tabular-nums; }
.ds-journal__repeat { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-style: italic; }
.ds-journal__note, .ds-journal__meta { margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-journal__empty { margin: 0; color: var(--ds-ink-2); }
.ds-journal__paging { display: flex; gap: var(--ds-space-3); align-items: center; margin: 0 0 var(--ds-space-6); font-size: var(--ds-fs-caption); }

/* --- план цепочки перезапуска (docs/31 §Конструктор, docs/32 §3) --------
   Владелец подтверждает не действие, а ПОРЯДОК ШАГОВ, поэтому план читается
   как список решений: номер, что произойдёт, кого трогает и с какого шага
   начинаются деньги. Шаг, который нечем исполнить, и шаг с расходом обязаны
   быть видны боковой полосой, а не подписью в конце строки. */
.ds-chain-plan { margin: var(--ds-space-4) 0; }
.ds-chain-plan__recipe { margin: 0; }
.ds-chain-plan__note { margin: var(--ds-space-1) 0 var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-chain-plan__steps { display: grid; gap: var(--ds-space-2); margin: 0; padding: 0; list-style: none; counter-reset: none; }
.ds-chain-plan__step { padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-line); border-left: 3px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-chain-plan__step--money { border-left-color: var(--ds-danger); background: var(--ds-danger-bg); }
.ds-chain-plan__step--gap { border-left-color: var(--ds-warn); }
.ds-chain-plan__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); margin: 0; }
.ds-chain-plan__no { font-size: var(--ds-fs-caption); color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-chain-plan__title { color: var(--ds-ink); }
.ds-chain-plan__target { padding: 0 6px; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); font-size: var(--ds-fs-label); color: var(--ds-ink-2); }
.ds-chain-plan__money { padding: 0 6px; border: 1px solid var(--ds-danger); border-radius: var(--ds-radius-s); font-size: var(--ds-fs-label); font-weight: 600; color: var(--ds-danger); }
.ds-chain-plan__wait { font-size: var(--ds-fs-label); color: var(--ds-ink-2); }
.ds-chain-plan__detail { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-chain-plan__gap { margin: var(--ds-space-1) 0 0; color: var(--ds-warn); font-size: var(--ds-fs-caption); font-weight: 600; }

/* Чек-лист цепочки (31.08): владелец спрашивает «что сделано по шагам», а не
   «какие были переходы». Отсюда крупная отметка слева, факт под шагом и
   причина стоянки со своей кнопкой. */
.ds-chain-check { margin: var(--ds-space-4) 0 var(--ds-space-5); }
.ds-chain-check__bar { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--ds-space-2) var(--ds-space-4); margin-bottom: var(--ds-space-3); }
.ds-chain-check__state { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); margin: var(--ds-space-1) 0 0; }
.ds-chain-check__links { display: flex; flex-wrap: wrap; gap: var(--ds-space-2); }
.ds-chain-check__stop { margin: 0 0 var(--ds-space-3); padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-danger); border-radius: var(--ds-radius); background: var(--ds-danger-bg); }
.ds-chain-check__stop-why { margin: 0; color: var(--ds-ink); font-weight: 600; }
.ds-chain-check__stop-also, .ds-chain-check__stop-todo { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-chain-check__times { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-weight: 400; }
.ds-chain-check__stop-action { margin: var(--ds-space-3) 0 0; }
.ds-chain-check__steps { display: grid; gap: var(--ds-space-2); margin: 0; padding: 0; list-style: none; }
.ds-chain-check__step { display: grid; grid-template-columns: 32px minmax(0, 1fr); align-items: start; gap: var(--ds-space-3); padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-line); border-left: 3px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-chain-check__step--ok { border-left-color: var(--ds-accent); }
.ds-chain-check__step--warn { border-left-color: var(--ds-warn); }
.ds-chain-check__step--bad { border-left-color: var(--ds-danger); background: var(--ds-danger-bg); }
.ds-chain-check__step--muted { color: var(--ds-ink-2); }
.ds-chain-check__glyph { font-size: 20px; line-height: 1.3; }
.ds-chain-check__body { min-width: 0; }
.ds-chain-check__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); margin: 0; }
.ds-chain-check__no { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; }
.ds-chain-check__title { color: var(--ds-ink); }
.ds-chain-check__mark { margin-left: auto; font-size: var(--ds-fs-label); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.ds-chain-check__mark--ok { color: var(--ds-accent); }
.ds-chain-check__mark--warn { color: var(--ds-warn); }
.ds-chain-check__mark--bad { color: var(--ds-danger); }
.ds-chain-check__mark--muted { color: var(--ds-ink-2); }
.ds-chain-check__fact { margin: var(--ds-space-1) 0 0; color: var(--ds-ink); }
.ds-chain-check__fact time { color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-chain-check__reason { margin: var(--ds-space-1) 0 0; color: var(--ds-danger); font-weight: 600; }
.ds-chain-check__detail { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-chain-check__step-action { margin: var(--ds-space-2) 0 0; }
.ds-chain-check__excluded { margin: 0 0 var(--ds-space-5); }
@media (max-width: 720px) {
  .ds-chain-check__step { grid-template-columns: 24px minmax(0, 1fr); }
  .ds-chain-check__mark { margin-left: 0; }
}

/* Свёрнутая цепочка в ленте журнала: одна строка-сводка, детали в раскрытии. */
.ds-journal__chain { background: var(--ds-surface-2); }
.ds-journal__chain-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--ds-space-2) var(--ds-space-4); }
.ds-journal__chain-summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); min-width: 0; margin: 0; }
.ds-journal__chain-text { color: var(--ds-ink); overflow-wrap: anywhere; }
.ds-journal__chain-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-3); }
.ds-journal__chain-actions .ds-stats-row__toggle { min-height: 44px; font-size: var(--ds-fs-caption); }
.ds-journal__chain-detail { margin-top: var(--ds-space-3); }
.ds-journal__chain-detail .ds-journal { margin-bottom: 0; }
.ds-journal__chain-detail .ds-journal__item { background: var(--ds-surface); }

/* Исход прямо на строке ленты: слово и цвет без раскрытия (владелец, 01.09).
   Идёт сразу за именем кампании и НЕ прижимается к правому краю: прижатый
   уезжал на вторую строку от одной длинной причины, а читают его первым. */
.ds-journal__outcome { font-weight: 600; overflow-wrap: anywhere; }
.ds-journal__outcome--ok { color: var(--ds-accent); }
.ds-journal__outcome--warn { color: var(--ds-warn); }
.ds-journal__outcome--bad { color: var(--ds-danger); }
.ds-journal__outcome--muted { color: var(--ds-ink-2); }
/* Сводка группы ужимается САМА, а не роняет кнопки на вторую строку: без
   базиса flex-wrap уносит их вниз из-за десятка лишних пикселей. */
.ds-journal__chain-summary { flex: 1 1 320px; }
.ds-journal__chain-actions { flex: 0 0 auto; }
.ds-journal__head .ds-journal__title { color: var(--ds-ink); font-weight: 600; }
.ds-journal__head .ds-journal__object { margin-left: 0; }
/* Номер кампании и логин кабинета — мета при имени: узнают по имени, сверяют
   по номеру. Не рвутся переносом: «№» без своего номера — обрывок. */
.ds-journal__object-id { margin-left: var(--ds-space-2); color: var(--ds-ink-3); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Служебная работа очереди внутри раскрытия: видна, но не спорит с делом. */
.ds-journal__item--service { opacity: 0.65; }
.ds-journal__item--service .ds-journal__head { font-size: var(--ds-fs-caption); }

/* Дата запуска кампании: одна строка под именем и в отдельной колонке
   экрана отбора. Цифры табличные — колонку дат читают глазом сверху вниз. */
.ds-stats-row__started { font-variant-numeric: tabular-nums; }
.ds-campaign-started { margin: 0 0 var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; }
/* Номер кампании (13.09): владелец сверяет строку с кабинетом Директа по
   номеру, а не по имени. На карточке — своя строка тем же тоном, что дата
   запуска; в списках — мелкая кликабельная подпись рядом с именем. */
.ds-campaign-number { margin: 0 0 var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; }
.ds-campaign-number--pair a { color: inherit; }
.ds-row-number { color: var(--ds-ink-2); font-variant-numeric: tabular-nums; text-decoration: none; }
.ds-row-number:hover { text-decoration: underline; }

/* Заголовок секции со своей кнопкой справа: вход в отбор стоит там, где
   владелец смотрит на список кампаний, а не в подвале страницы. */
.ds-section-bar { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--ds-space-3); margin: var(--ds-space-5) 0 var(--ds-space-3); }
.ds-section-bar .ds-section-title { margin: 0; }
.ds-section-note { margin: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

/* Экран отбора по условию (docs/31 §Каталог команд). */
.ds-select-presets { margin: 0 0 var(--ds-space-4); }
/* Колонка ОДНА и она ужимается: без ``minmax(0, 1fr)`` грид без шаблона
   делает колонку по max-content — длинная приписка пресета не переносится
   вовсе и уезжает за край экрана. */
.ds-select-presets__list { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ds-space-2); margin: 0 0 var(--ds-space-2); padding: 0; list-style: none; }
.ds-select-presets__list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); }
.ds-select-presets__note { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-select-presets__hint { margin: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
/* ЗАКРЕПЛЁННЫЕ ПРЕДНАСТРОЙКИ — ряд кнопок над таблицей. Их шесть: ряд из
   десяти на ноутбуке уезжает в горизонтальную прокрутку, и владелец
   пролистывает кнопки вместо того, чтобы выбирать. Полный список стоит
   раскрывашкой ниже — и он же служит выпадающим списком на телефоне, где
   ряд закреплённых прячется совсем: на 380 px пять кнопок с числами
   занимают три строки и отжимают таблицу вниз. */
.ds-select-presets__pinned { display: flex; flex-wrap: wrap; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-2); padding: 0; list-style: none; }
.ds-select-presets__all { margin: 0 0 var(--ds-space-2); }
.ds-select-presets__all > summary { cursor: pointer; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-select-presets__all > summary:hover { color: var(--ds-ink); }
.ds-select-presets__all .ds-select-presets__list { margin-top: var(--ds-space-2); }
/* «Условие изменено вручную» — предупреждение, а не подпись: пока метка
   преднастройки стоит, её кнопка выглядит нажатой, и владелец обязан видеть,
   что список уже не совпадает с ней. */
.ds-select-presets__changed { color: var(--ds-warn); }

/* ПАНЕЛЬ «СТОЛБЦЫ» — состав таблицы и три плоскости (владелец 18.09:
   «столбцы должны быть настраиваемые», «смотри в разных плоскостях»).
   Стоит СПРАВА над таблицей и открывается нативным <details>: ни строчки
   скрипта, работает с клавиатуры и с телефона.
   Выпадает НАД содержимым (position: absolute), а не раздвигает страницу:
   список из двадцати строк, раздвигающий её, уводил бы таблицу за нижний
   край экрана ровно в тот момент, когда владелец её настраивает. На узком
   экране абсолютное позиционирование снимается — там ему негде выпадать. */
.ds-select-toolbar { display: flex; justify-content: flex-end; margin: 0 0 var(--ds-space-2); }
.ds-select-columns { position: relative; }
.ds-select-columns__open { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill); background: var(--ds-surface); color: var(--ds-ink); cursor: pointer; font-size: var(--ds-fs-caption); list-style: none; white-space: nowrap; }
.ds-select-columns__open::-webkit-details-marker { display: none; }
.ds-select-columns__open:hover { border-color: var(--ds-line); }
.ds-select-columns[open] .ds-select-columns__open { border-color: var(--ds-ink-2); }
.ds-select-columns__body { position: absolute; right: 0; z-index: 30; width: 360px; max-width: calc(100vw - var(--ds-space-4) * 2); margin-top: var(--ds-space-2); padding: var(--ds-space-3); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-2); background: var(--ds-surface); box-shadow: 0 12px 28px -12px rgba(22, 24, 28, .28); text-align: left; }
.ds-select-columns__planes { display: flex; flex-wrap: wrap; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-3); padding: 0; border: 0; }
.ds-select-columns__planes legend { padding: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-weight: 600; }
/* ПРОКРУЧИВАЕТСЯ СПИСОК, А НЕ ПАНЕЛЬ. Кнопки плоскостей сверху и «Сохранить»
   снизу обязаны быть на виду всегда: уехавшая под край кнопка означала бы,
   что владелец правит галочки, не видя, чем закончить, — а прибитая поверх
   списка (sticky) закрывала бы собой строки, которые он в этот момент
   правит. Прокручивается ровно то, что длинное. */
.ds-select-columns__list { display: flex; flex-direction: column; gap: 2px; max-height: 46vh; overflow-y: auto; margin: 0 0 var(--ds-space-3); padding: 0; list-style: none; }
.ds-select-columns__item { display: flex; align-items: center; gap: var(--ds-space-2); padding: 2px 4px; border-radius: var(--ds-radius-1); }
.ds-select-columns__item:hover { background: var(--ds-surface-2); }
.ds-select-columns__item .ds-select-form__check { flex: 1 1 auto; min-width: 0; margin: 0; }
/* Обязательный столбец ГАСНЕТ, А НЕ ПРЯЧЕТСЯ: владелец обязан видеть, что он
   есть, и понимать, почему его не снять. Причина — в подсказке галочки. */
.ds-select-columns__item--locked { color: var(--ds-ink-2); }
.ds-select-columns__absent { color: var(--ds-warn); font-size: var(--ds-fs-caption); white-space: nowrap; }
.ds-select-columns__move { display: inline-flex; flex: 0 0 auto; gap: 2px; }
/* Стрелки мельче кнопок экрана: их двадцать пар в одном списке, и на высоте
   рабочей кнопки (38 px) панель уезжает под нижний край экрана целиком. Цель
   нажатия при этом остаётся 26×26 — не «крошечная стрелка», а квадрат. */
.ds-select-columns__move .ds-button { min-width: 26px; min-height: 26px; padding: 0 6px; }
.ds-select-columns__hint { margin: 0 0 var(--ds-space-2); }
/* Кнопка неявной отправки: место занимает нулевое, видна не бывает. Убирать её
   через display: none нельзя — спрятанная так кнопка перестаёт быть кнопкой
   отправки по умолчанию, и Enter снова достался бы первой плоскости. */
.ds-select-columns__default { position: absolute; width: 0; height: 0; padding: 0; border: 0; opacity: 0; overflow: hidden; }
.ds-select-columns__form .ds-select-form__go { margin: 0; flex-wrap: wrap; }
/* «Не сохранено» — предупреждением, а не серой подписью: правки со скриптом
   копятся в панели до нажатия, и владелец обязан видеть, что запись ещё не
   случилась. Пустой строка стоит всегда, пока правок нет. */
.ds-select-columns__note { flex: 1 1 100%; color: var(--ds-warn); font-size: var(--ds-fs-label); }

@media (max-width: 900px) {
  /* На телефоне таблицы нет вовсе — строки идут карточками, — и выпадающей
     панели негде выпадать: она встаёт обычным блоком в поток. */
  .ds-select-toolbar { justify-content: flex-start; }
  .ds-select-columns__body { position: static; width: auto; max-width: none; }
}

/* ВЕРДИКТ ОЦЕНЩИКА В СТРОКЕ. Слово И цвет: по цвету одному читают те, кто
   цвет различает, и никто из них не читает по нему, ЧТО измерили. Причина
   открывается НАЖАТИЕМ, а не наведением: подсказка по наведению недоступна
   ни с клавиатуры, ни с телефона, а строку с этим вердиктом владелец
   собирается остановить. */
.ds-select-row__verdict { vertical-align: top; }
.ds-verdict > summary { cursor: pointer; list-style: none; }
.ds-verdict > summary::-webkit-details-marker { display: none; }
.ds-verdict__word { display: inline-flex; align-items: center; padding: 0 7px; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill); background: var(--ds-surface); color: var(--ds-ink-2); font-size: 11px; font-weight: 600; line-height: 18px; white-space: nowrap; }
.ds-verdict__word--bad { border-color: #eccfcf; background: #fbf1f1; color: var(--ds-danger); }
.ds-verdict__word--warn { border-color: #e8ddc0; background: #fbf7ec; color: var(--ds-warn); }
.ds-verdict__word--ok { border-color: #cfe3d9; background: #f1f7f4; color: var(--ds-accent); }
/* «Не оценивалось» и «недостаточно данных» обязаны выглядеть иначе, чем
   вердикт: это отсутствие суждения, а не мягкое суждение. Пунктир — тот же
   приём, которым витрина отличает «города не знаем» от названного города. */
.ds-verdict__word--none { border-style: dashed; color: var(--ds-ink-3); }
.ds-verdict__why { max-width: 320px; margin-top: var(--ds-space-1); color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-verdict__why p { margin: 0 0 var(--ds-space-1); }
.ds-verdict__basis { color: var(--ds-ink-3); }

/* РАЗВОРОТ СТРОКИ: то, чему не место в узкой ячейке, — на месте и без ухода
   с экрана. Нативный <details>: клавиатура и телефон работают сами. */
.ds-row-more { margin-top: var(--ds-space-1); }
.ds-row-more__open { cursor: pointer; color: var(--ds-ink-3); font-size: var(--ds-fs-label); }
.ds-row-more__open:hover { color: var(--ds-ink); }
.ds-row-more__body { max-width: 420px; margin-top: var(--ds-space-2); padding: var(--ds-space-2); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface-2, rgba(0,0,0,.03)); }
.ds-row-more__facts { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--ds-space-1) var(--ds-space-3); margin: 0 0 var(--ds-space-2); font-size: var(--ds-fs-label); }
.ds-row-more__facts dt { color: var(--ds-ink-3); }
.ds-row-more__facts dd { margin: 0; color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.ds-row-more__spark { margin: 0 0 var(--ds-space-2); }
.ds-row-more__last { margin: 0 0 var(--ds-space-1); font-size: var(--ds-fs-label); }
/* Причина у погашенной команды. Приглушённая и своей строкой: она объясняет
   кнопку, а не спорит с ней. */
.ds-row-more__why { margin: 0 0 var(--ds-space-1); color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
/* Последнее действие по кампании — под кнопками её строки. Не то же, что чип
   «по вашему действию»: тот гаснет, как только сбор подтвердил объявленное,
   а память о нажатии владельцу нужна и после. */
.ds-select-row__last { display: block; margin-top: 4px; color: var(--ds-ink-3); font-size: var(--ds-fs-label); }
@media (max-width: 720px) {
  .ds-select-presets__pinned { display: none; }
}
.ds-select-form { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--ds-space-4); margin: 0 0 var(--ds-space-4); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-select-form__block { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2); margin: 0; padding: 0; border: 0; }
.ds-select-form__block legend { padding: 0; font-size: var(--ds-fs-label); letter-spacing: .06em; text-transform: uppercase; color: var(--ds-ink-2); }
.ds-select-form__check, .ds-select-form__field { display: inline-flex; align-items: center; gap: 6px; min-height: 34px; font-size: var(--ds-fs-caption); }
.ds-select-form__check input { width: 18px; height: 18px; }
.ds-select-form__field input[type="number"] { width: 96px; min-height: 34px; }
.ds-select-form__field select { min-height: 34px; }
.ds-select-form__hint { color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-select-form__go { display: flex; align-items: center; gap: var(--ds-space-2); margin-left: auto; }
.ds-select-summary { margin: 0 0 var(--ds-space-4); font-variant-numeric: tabular-nums; }
/* «Скрыто: N заблокированных · M без показателей» над таблицами статистики.
   Стоит вплотную к таблице, которую объясняет, и не спорит с ней тоном:
   счётчик спрятанного — подпись к таблице, а не предупреждение. */
.ds-hidden-rows { margin: 0 0 var(--ds-space-2); color: var(--ds-ink-2); font-size: var(--ds-fs-label); font-variant-numeric: tabular-nums; }
/* Фильтр по городу и чип города в строке (заказ владельца 05.09). Чип стоит
   в СТРОКЕ, а не отдельной колонкой: колонок в таблице и так на 125 px
   больше ширины экрана (замер 01.09), и город — свойство имени кампании, а
   не ещё одно число. Непомеченный чип бледнее и пунктиром: «города не
   знаем» обязано выглядеть иначе, чем «город такой-то». */
/* Предложения завести проект трекера (заказ владельца 05.09). Карточка
   заметная и жёлтая: это вопрос владельцу, а не сводка. Отклонённые уезжают
   в свёрнутый список — решение обратимо, а не стёрто. */
.ds-offers { margin: 0 0 var(--ds-space-4); padding: var(--ds-space-3); border: 1px solid var(--ds-warn); border-radius: var(--ds-radius-2, 6px); }
.ds-offer { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; gap: var(--ds-space-2); padding: var(--ds-space-2) 0; border-bottom: 1px solid var(--ds-line); }
.ds-offer:last-child { border-bottom: 0; }
.ds-offer__what { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--ds-space-2); }
.ds-offer__slug { color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-offer__why { flex-basis: 100%; color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-offer__do { display: flex; gap: var(--ds-space-2); }
.ds-offers-dismissed { margin: 0 0 var(--ds-space-4); color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-dynamics__scope { margin: var(--ds-space-1) 0 0; color: var(--ds-warn); font-size: var(--ds-fs-label); }
.ds-city-filter { display: flex; align-items: center; flex-wrap: wrap; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-2); }
.ds-city-filter__field { display: inline-flex; align-items: center; gap: var(--ds-space-1); color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-city-filter__note { color: var(--ds-ink-2); font-size: var(--ds-fs-label); font-variant-numeric: tabular-nums; }
.ds-city-filter__reset { color: var(--ds-accent); font-size: var(--ds-fs-label); }
.ds-stats-row__city { display: inline-block; margin-top: 2px; padding: 0 var(--ds-space-1); border-radius: var(--ds-radius-1, 3px); font-size: var(--ds-fs-label); line-height: 1.6; white-space: nowrap; }
.ds-stats-row__city--known { color: var(--ds-ink); background: var(--ds-surface-2, rgba(0,0,0,.05)); }
.ds-stats-row__city--unknown { color: var(--ds-ink-2); border: 1px dashed var(--ds-line); }
.ds-model-split__note--warn { color: var(--ds-warn); }
.ds-stats-row__hidden-note { font-style: italic; }
.ds-select-group { margin: 0 0 var(--ds-space-5); }
/* Кабинетный блок на экране подтверждения пакета: над ним стоят красные
   линии, и заголовок кабинета не должен к ним прилипать — иначе имя читается
   как продолжение предупреждения. */
.ds-select-group--batch { margin-top: var(--ds-space-5); }
.ds-select-group__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-3); margin: 0 0 var(--ds-space-2); }
.ds-select-group__head .ds-section-title { margin: 0; }
.ds-select-group__meta { margin: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-select-group__head .ds-button { margin-left: auto; }
/* Панель пакета над ВСЕМ отбором. Липкость и вид у неё те же, что у панели
   кабинета (``.ds-campaign-batch``): это одна и та же вещь — «выбрано N,
   вот что можно сделать», — и второй её внешности быть не должно. Своё здесь
   только одно: панель отбора стоит над несколькими кабинетами, поэтому
   счётчик называет ещё и их число. */
.ds-select-panel { margin-bottom: var(--ds-space-4); }
/* Кнопки кабинета: своя строка под заголовком группы, а не в одном ряду с
   ним. В ряду с «Выбрать все» четыре глагола расползались в кашу и на
   ноутбуке переносились по одному на строку. */
.ds-select-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-2); }
.ds-select-actions__note { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; }
/* Предупреждение про архив: своя строка и цвет предупреждения. Кнопка при
   этом НЕ гаснет — гейтит сервер, и владелец вправе нажать, увидев, сколько
   строк откажет поимённо. */
.ds-select-actions__warn { flex: 1 0 100%; color: var(--ds-warn); font-size: var(--ds-fs-caption); }
/* Скрытая подпись обязана быть скрытой: «hidden» из разметки правит скрипт, и
   любое авторское display: тут перебило бы браузерное правило молча. */
.ds-select-actions__warn[hidden] { display: none; }
.ds-select-actions .ds-button[disabled], .ds-select-panel .ds-button[disabled] { opacity: .5; cursor: default; }
/* Строка, которую отметить нельзя (архив, забаненный кабинет, идущая
   цепочка), приглушена по макету: она в списке есть и объяснена, но
   решением не является. */
/* ИТОГОВАЯ СТРОКА — сверху и по тому же отбору, что под ней. Выделена фоном
   и верхним правилом: это не кампания, и спутать её со строкой списка нельзя
   ни на миг. Липкой она не делается намеренно — прибитая раскладка и так
   держит колонки, а вторая прилипающая полоса отнимала бы высоту у списка. */
.ds-select-row--total { background: var(--ds-surface-2, #f4f5f7); font-weight: 600; }
.ds-select-row--total td { border-bottom: 2px solid var(--ds-line); }
.ds-select-row--total .ds-stats-row__meta { font-weight: 400; }

.ds-select-row--blocked { opacity: .55; }
/* СТРОКА В РАБОТЕ — НЕ ПРИГЛУШЁННАЯ, А ПОДСВЕЧЕННАЯ. Галочки у неё нет по той же
   причине, что и у спрятанных (второе нажатие завело бы второго клона), но смысл
   обратный: владелец пришёл на экран УБЕДИТЬСЯ, что работа пошла. Тот же тёплый тон,
   что у знака цепочки. */
.ds-select-row--working { background: #fbf6ea; }
.ds-select-row__note { color: var(--ds-warn); }
.ds-select-row__started { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Имя и знак-пометка на одной строке: знак идёт ЗА именем и не разрывает его
   при переносе длинного названия. */
.ds-select-row__title { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--ds-space-2); }
/* Знак «МК» вместо фразы в две строки. Причина у всех таких строк одна и не
   меняется никогда — значит, ей место в знаке с подсказкой, а не в тексте
   каждой строки: на боевом кабинете оранжевых слов было 10 из 21 строки, и
   между ними терялись сами кампании. */
/* Знаки строки по макету design-otbor: 11px/600, рамка, радиус 4, поля 0 5px.
   Приглушённый — про устройство кампании («МК»), зелёный — про её историю
   («перезапущена 01.09», ссылка на карточку цепочки). */
.ds-select-row__badge { flex: none; padding: 0 5px; border: 1px solid var(--ds-line-2); border-radius: 4px; background: var(--ds-surface-2); color: var(--ds-ink-2); font-size: 11px; font-weight: 600; line-height: 16px; cursor: help; text-decoration: none; }
.ds-select-row__badge--ok { border-color: #cfe3d9; background: #f1f7f4; color: var(--ds-accent); }
/* Идущая цепочка. Тон приходит от самой цепочки (campaign_restart.CHAIN_TONE)
   — тот же, которым её ход подписан в строке кабинета: одно событие обязано
   выглядеть одинаково на всех экранах. */
.ds-select-row__badge--warn { border-color: #e8d9b8; background: #fbf6ea; color: var(--ds-warn); }
.ds-select-row__badge--bad { border-color: #e8c9c9; background: #fbeeee; color: var(--ds-danger); }
a.ds-select-row__badge:hover { border-color: var(--ds-accent); }
/* Ручки скрытого в блоке фильтров: своя строка во всю ширину, счётчик — по
   правому краю, как в макете. */
.ds-select-form__block--wide { grid-column: 1 / -1; }
.ds-select-hidden { display: flex; align-items: center; gap: var(--ds-space-4); flex-wrap: wrap; }
.ds-select-hidden__count { margin-left: auto; color: var(--ds-ink-3); font-size: var(--ds-fs-caption); }
/* Число у галочки фильтра: сколько строк попадёт в эту корзину. Табличные
   цифры и приглушённый тон — это подпись к галочке, а не второе слово в ней. */
.ds-select-form__count { color: var(--ds-ink-3); font-variant-numeric: tabular-nums; }
.ds-select-form__field--grow { flex: 1 1 260px; }
.ds-select-form__field--grow input { flex: 1 1 auto; min-width: 0; min-height: 34px; }
/* ПОЛОСА БЫСТРЫХ ОТВЕТОВ ПРО АКТИВНОСТЬ. Стоит между командами и формой:
   владелец сначала спрашивает «что жжёт / что молчит», и только если ответа
   мало — уходит в форму. Чипы те же, что у команд: один вид у ссылки,
   ставящей условие. */
/* ФИЛЬТРЫ В РАСКРЫВАШКЕ (Ф1 слияния поверхностей, 17.09). На объединённом
   экране проекта таблица кампаний стоит под сводкой кабинетов, и двенадцать
   растопыренных fieldset'ов отодвигали бы её на второй экран прокрутки:
   владелец приходит СМОТРЕТЬ СПИСОК, а не заполнять форму. Полоса
   преднастроек, чипы активности и чипы кабинетов остаются НАД раскрывашкой —
   ими сужают в девяти случаях из десяти. */
.ds-select-filters { margin: 0 0 var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-2); background: var(--ds-surface); }
.ds-select-filters__open { padding: var(--ds-space-3); font-weight: 600; cursor: pointer; }
.ds-select-filters[open] .ds-select-filters__open { border-bottom: 1px solid var(--ds-line); }
.ds-select-filters .ds-select-form { border: 0; }

/* КАБИНЕТЫ ЧИПАМИ. Кабинет — первое, чем сужают таблицу проекта, и прятать
   его в блок фильтров рядом с одиннадцатью другими значило бы заставлять
   разворачивать форму ради самого частого вопроса. */
.ds-select-cabinets { margin: 0 0 var(--ds-space-4); }
.ds-select-cabinets__list { display: flex; flex-wrap: wrap; gap: var(--ds-space-2); margin: 0 0 var(--ds-space-2); padding: 0; list-style: none; }

.ds-select-activity { margin: 0 0 var(--ds-space-4); }
.ds-select-activity__list { display: flex; flex-wrap: wrap; gap: var(--ds-space-2); margin: 0; padding: 0; list-style: none; }
/* СТАТУС ЖИВЫМ ЧИПОМ. Цвет читается раньше слова, поэтому он есть у каждого
   состояния и различает четыре разных вещи: работает (акцент), ждёт решения
   модерации (предупреждение), отклонена (опасность), стоит или в архиве
   (приглушение разной силы). Рамка, а не заливка: строка таблицы не должна
   превращаться в светофор. */
.ds-state-chip { display: inline-flex; align-items: center; gap: 5px; padding: 0 7px; border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill); background: var(--ds-surface); color: var(--ds-ink-2); font-size: 11px; font-weight: 600; line-height: 18px; white-space: nowrap; }
.ds-state-chip--ok { border-color: #cfe3d9; background: #f1f7f4; color: var(--ds-accent); }
.ds-state-chip--warn { border-color: #e8ddc0; background: #fbf7ec; color: var(--ds-warn); }
.ds-state-chip--bad { border-color: #eccfcf; background: #fbf1f1; color: var(--ds-danger); }
.ds-state-chip--muted { color: var(--ds-ink-2); }
.ds-state-chip--faded { border-style: dashed; color: var(--ds-ink-3); }
/* Накладка «по вашему действию»: состояние, которое инструмент записал в
   кабинет и подтвердил, но которого следующий каталожный сбор ещё не видел.
   Точка перед словом отличает утверждение журнала от собранного факта —
   молча подменять одно другим нельзя. */
.ds-state-chip--applied { margin-left: 6px; }
/* Включена, а показов нет: чип стоит ВТОРЫМ, рядом с состоянием, потому что
   тревожен не он сам, а его пара с чипом «работает» слева. */
.ds-state-chip--silent { margin-left: 6px; }
.ds-state-chip__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.ds-select-row__applied-note { display: block; }
.ds-select-row__seen { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ds-select-row__city { white-space: nowrap; }
/* Команды строки — МЕЛКИМИ СЛОВАМИ В РЯД, как «Редактировать · Статистика»
   у Директа, а не столбиком рамочных кнопок. Столбик из четырёх кнопок держал
   строку в 166 px: на экран 1200 px влезало четыре кампании из тысячи.
   Недоступных команд здесь больше нет вовсе — их причины стоят в развороте
   строки, и место они не занимают. */
.ds-select-row__buttons { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 8px; }
.ds-row-act { color: var(--ds-accent); font-size: 12px; line-height: 16px; text-decoration: none; white-space: nowrap; }
.ds-row-act:hover { text-decoration: underline; }
/* ЛИПКИЙ СЧЁТЧИК. Список бывает на сотню строк, и до 08.09 «выбрано: 12»
   уезжало вверх при первой же прокрутке: владелец отмечал галочки вслепую и
   не видел ни числа, ни кнопок, которыми это нажимается. Панель остаётся
   на экране всю прокрутку таблицы. */
/* 56px — высота липкой шапки витрины (.ds-topbar). Панель встаёт ПОД неё, а
   не за ней: прилипнув к нулю, она уезжала бы под шапку и счётчик снова
   пропадал бы из виду — ровно та беда, ради которой липкость и добавлена. */
.ds-select-panel--sticky { position: sticky; top: 56px; z-index: 3; padding: var(--ds-space-2) var(--ds-space-3); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface); box-shadow: 0 1px 0 var(--ds-line); }
@media (max-width: 720px) {
  /* На узком экране шапка переносит навигацию во вторую строку, и её высота
     перестаёт быть постоянной. Липкость там снимается: гадать смещение — это
     панель, наезжающая на строки, а неподвижная панель хотя бы не врёт. */
  .ds-select-panel--sticky { position: static; }
}
/* Распорка на месте галочки у строки, которую отметить нельзя. Без неё имя
   такой строки уезжало влево на ширину чекбокса, и таблица читалась как
   сбитая. Место занимается ровно то же — из одной переменной с самой
   галочкой. */
.ds-campaign-pick--empty { display: inline-block; }
/* Галочка не сжимается. По умолчанию flex-элемент ужимается под соседа, и на
   строках с длинным именем чекбокс худел на 5 px — имена стояли по РАЗНЫМ
   левым краям (58 и 63 px на боевом экране), и таблица читалась как сбитая.
   Распорка на месте галочки у неотмечаемой строки держит тот же размер. */
/* Поля обнуляются явно: у <input> они приходят от браузера (слева ~4 px), у
   распорки-<span> их нет, и строка без галочки стояла на 4 px левее соседних.
   Отступ теперь один и написан здесь, а не достаётся от UA-стилей. */
.ds-campaign-pick, .ds-campaign-pick--empty { flex: none; box-sizing: border-box; width: 18px; height: 18px; margin: 0 8px 0 0; }
/* Имя — самый широкий столбец: в него уезжают и модель, и кабинет, и пометки,
   а имена кампаний парка длинные («Линзы||РСЯ||Целевые||Изображения||0202 ·
   перезапуск с-15993ca39615»). Место под него берётся у столбца кнопки:
   кнопке хватает своей ширины, а имени в три строки — нет. */
.ds-table__th--name { min-width: 300px; }
/* Столбец команд. Ширина ЯВНАЯ, а не «по содержимому»: у таблицы отбора
   раскладка прибита (table-layout: fixed), и при ней «width: 1%» значит
   буквально один процент — слова вылезали бы за колонку на соседнюю. 124 px —
   это «Перезапустить» мелким кеглем (12 px × 13 знаков) со знаком меню «⋯» в
   том же ряду и заголовок «Что сделать» в одну строку. Число входит в сумму ширин ниже (ds.css §«таблица
   отбора») — правишь здесь, правь и min-width таблицы; на это стоит тест. */
.ds-select-row__action { width: 124px; }
/* Заголовок-переключатель порядка переносится сам, где место позволяет; в
   таблице кампаний его перенос снят отдельным правилом плотности — там
   заголовок обязан быть в одну строку. */
.ds-table__th.ds-stats-sort { white-space: normal; }

/* Состояние — короткое слово в одну строку. Перенос «состояние не отдаётся»
   на две строки поднимал высоту КАЖДОЙ строки мастера. */
.ds-select-row__state { white-space: nowrap; }
/* Картинка по дням в строке отбора — та же ширина, что у спарклайна витрины:
   страница «все РК разом» читается только пока строка остаётся строкой.
   Ширины на ДВА окна не удваиваются намеренно: дней вдвое больше, а место то
   же — столбики становятся уже, обрыв заявок виден так же. */
/* Та же привязка, что у колонки парка, но отступ здесь 1 px, а не 12:
   148 − 1 − 1 = 146 = SPARK_W. Это число НЕ сужается ради общей ширины
   таблицы: картинка нарисована на 146 px и рисуется один к одному, сжатая —
   теряет кегль подписей (страж — ``tests/console/test_ds.py``). */
.ds-select-row__spark { width: 148px; min-width: 148px; max-width: 148px; }
.ds-select-row__spark-head { width: 148px; min-width: 148px; max-width: 148px; }
.ds-select-row__spark .ds-spark { height: 45px; }
/* «Было → стало»: прошлое число приглушено, нынешнее — обычной яркости.
   Владелец читает строку слева направо и видит падение, а не два равных
   числа рядом. */
.ds-select-summary--unjudged { color: var(--ds-warn); }
.ds-select-row__history { white-space: nowrap; font-variant-numeric: tabular-nums; }
.ds-select-row__was { color: var(--ds-ink-2); }
.ds-select-row__arrow { color: var(--ds-ink-2); padding: 0 .35rem; }
.ds-campaign-confirm__origin { margin: 0 0 var(--ds-space-3); padding: var(--ds-space-2) var(--ds-space-3); border-left: 3px solid var(--ds-line-2); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

/* --- ширина таблицы статистики (аудит 01.09) ---------------------------- */
/* Задача владельца: «есть горизонтальная прокрутка в таблицах, открыть и
   показать кампании за зоной показа». Замер фактом (playwright, боевые
   данные): экран проекта на 1440 требовал 1515 px при доступных 1390 — за
   краем целиком оставалась колонка «Действие», то есть КНОПКИ. На 1280 туда
   же уезжали «Съём» и «Задач». Экран кабинета выезжал на 2 px при длинных
   именах кампаний и на 162 px на 1280.
   Лечится не прокруткой, а составом колонок (см. комментарии в
   stats_table_head.html и stats_table_row.html) плюс правилами ниже. Сама
   прокрутка остаётся страховкой для узких окон — .ds-stats-table__scroll не
   трогаем.
   Раздел ДОПОЛНЯЕТ файл и намеренно переобъявляет несколько правил выше:
   так правка не спорит с чужими строками в параллельной работе. */

/* Каркасный минимум: он считался от прежних 12 колонок и сам по себе
   заставлял таблицу скроллиться на 1280. Новый состав — 8/9 колонок. */
.ds-stats-table table { min-width: 900px; }
.ds-stats-nested__table { min-width: 860px; }
.ds-modal .ds-stats-table table { min-width: 640px; }

/* Числа читаются от верха строки: в строке с вертикальной пачкой кнопок
   (до 200 px высоты) выравнивание по центру уводило расход и заявки далеко
   вниз от имени, к которому они относятся. */
.ds-stats-table td, .ds-stats-nested__table td { vertical-align: top; }
.ds-stats-table th, .ds-stats-nested__table th { vertical-align: bottom; }

/* Единица измерения — отдельной строкой заголовка. У .ds-num стоит
   white-space: nowrap, поэтому «Потрачено с НДС» держала 136 px, а «Цена
   заявки с НДС» — 147 px при числах вдвое уже. */
.ds-stats-th__name { display: block; }
.ds-stats-th__unit { display: block; margin-top: 1px; color: var(--ds-ink-3); font-size: var(--ds-fs-tiny); font-weight: 400; }

/* Клики и показы одной колонкой: сверху клики, снизу приглушённо показы —
   ровно в порядке двухстрочного заголовка «Клики / показы». */
.ds-stats-row__traffic .ds-stats-row__muted { margin-top: 2px; }

/* Признак «тратит и не даёт заявок» переехал сюда с убранной колонки «Итог»:
   он и должен стоять на самом числе денег, а не в дубле рядом. */
.ds-stats-row__spend--burning { color: var(--ds-danger); }
.ds-stats-row__spend--burning .ds-stats-row__delta { color: var(--ds-danger); }

/* Съём и «ожидает подтверждения» — строкой-метой под именем: отдельными
   колонками они стоили 65 и 118 px ширины ради одной короткой подписи. */
.ds-stats-row__snap { display: inline-flex; align-items: baseline; gap: 4px; color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-stats-row__lead .ds-stats-row__toggle { margin-top: var(--ds-space-1); }
.ds-stats-row__snap-label { color: var(--ds-ink-3); }
.ds-stats-row__snap .ds-collection { font-size: var(--ds-fs-label); }
/* СОСТАВ ЗАЯВОК — строкой-метой рядом со съёмом, теми же мелкими буквами.
   Стоит ЗДЕСЬ, а не в клетке «Заявок»: у клеток плотной таблицы страхующая
   обрезка (``overflow: hidden``), а всплывашка состава позиционируется
   абсолютно — её срезало бы по краю клетки ровно в том месте, ради которого
   её открывают. Клетка имени обрезку не наследует по своему правилу. */
.ds-stats-row__leadmix { display: inline-flex; align-items: baseline; gap: 4px; color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-stats-row__pending { margin-top: 0; }

/* График занимает ровно свою ширину и не отбирает место у кнопок:
   было width 300 / min 200. Ширина СВЯЗАНА с SPARK_W в console/templatetags/ds.py:
   картинка вписывается в клетку целиком, и лишняя ширина viewBox сжимала бы
   вместе с ней и цифры. 170 − 12 − 12 отступа = 146 = SPARK_W.
   ТРИ ЗНАЧЕНИЯ ОДИНАКОВЫ НАМЕРЕННО. Пока стояло min 130 / max 220, обещание
   «кегль 11» держалось только на номинале: сжатая до минимума колонка давала
   картинке 106 px, масштаб 0,73 — и одиннадцать пунктов приезжали на экран
   восемью, ровно та беда, из-за которой ширину и привязывали. */
.ds-stats-row__spark { width: 170px; min-width: 170px; max-width: 170px; }
.ds-stats-row__spark-head { width: 170px; min-width: 170px; max-width: 170px; }

/* Разбивка по моделям больше не запрещает перенос: строка «МК 1 · 1 заявка
   КК 1 · 0 ОЗК 0 из 8 · 0» держала колонку имени на 308 px минимума. */
.ds-stats-row__models { flex-wrap: wrap; }

/* Колонка действия: кнопки целиком в зоне показа и не шире, чем нужно им
   самим. Верхняя граница обязательна — пояснение под заблокированной кнопкой
   («перезапуск по этой кампании уже идёт») тянуло колонку до 393 px и отбирало
   место у имени кампании. */
.ds-stats-row__actions { min-width: 148px; max-width: 260px; }
/* Кнопки в столбик одной ширины: столбик разной длины читается как лесенка. */
.ds-stats-row__actions .ds-campaign-control { align-items: stretch; }
.ds-stats-row__actions .ds-campaign-control__why { white-space: normal; text-align: right; }

/* ---------------------------------------------------------------------------
   Задачник («История», console:jobs). Претензия владельца 01.09: «сама
   история не особо понятна… не понятны дальнейшие шаги». Отсюда форма:
   группа → строка → одна кнопка. Главное (что за задание, чем кончилось,
   почему) читается со строки; машинная мета живёт в раскрытии и на layout
   не влияет — строка не прыгает от того, что её раскрыли.
   --------------------------------------------------------------------------- */
.ds-taskfilter { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--ds-space-3); margin: 0 0 var(--ds-space-5); padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius); background: var(--ds-surface-2); }
.ds-taskfilter__field { display: flex; flex-direction: column; gap: var(--ds-space-1); font-size: var(--ds-fs-label); text-transform: uppercase; letter-spacing: .06em; color: var(--ds-ink-2); }
.ds-taskfilter__field select, .ds-taskfilter__field input { min-height: 36px; padding: 0 var(--ds-space-2); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); font-size: var(--ds-fs-caption); text-transform: none; letter-spacing: 0; color: var(--ds-ink); }
.ds-taskfilter__error { flex: 1 0 100%; margin: 0; color: var(--ds-danger); font-size: var(--ds-fs-caption); }

.ds-taskgroup { margin: 0 0 var(--ds-space-6); scroll-margin-top: var(--ds-space-5); }
.ds-taskgroup__head { margin: 0 0 var(--ds-space-3); }
.ds-taskgroup__head .ds-section-title { display: flex; align-items: baseline; gap: var(--ds-space-2); margin: 0; }
/* Счётчик группы — рядом с её именем, а не в отдельной строке: владелец
   читает «Ждут вас · 6» одним движением глаза. */
.ds-taskgroup__count { min-width: 26px; padding: 1px var(--ds-space-2); border-radius: var(--ds-radius-pill); background: var(--ds-surface-2); font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; color: var(--ds-ink-2); text-align: center; }
.ds-taskgroup--wait .ds-taskgroup__count { background: var(--ds-warn-bg); color: var(--ds-warn); }
.ds-taskgroup--failed .ds-taskgroup__count { background: var(--ds-danger-bg); color: var(--ds-danger); }
.ds-taskgroup__note { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-reconciliation__delivery { margin: 0 0 var(--ds-space-2); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
/* Кабинет в ячейке имени: факт, а не столбец-доминанта. */
.ds-select-row__cabinet { color: var(--ds-ink-2); }
/* Оговорка к нулю заявок на окне с незрелым хвостом — своей строкой под числом. */
.ds-select-row__maturing { display: block; }
/* Строка обрезки: заметна, но не кричит — это не ошибка, а честная
   оговорка о потолке экрана, и у неё всегда есть выход ссылкой. */
.ds-taskgroup__cut { margin: var(--ds-space-2) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-taskgroup__empty { margin: 0; padding: var(--ds-space-3) var(--ds-space-4); border: 1px dashed var(--ds-line-2); border-radius: var(--ds-radius-s); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

.ds-tasklist { display: grid; gap: var(--ds-space-2); margin: 0; padding: 0; list-style: none; }
.ds-task { padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-line); border-left: 3px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-task--warn { border-left-color: var(--ds-warn); background: var(--ds-warn-bg); }
.ds-task--bad { border-left-color: var(--ds-danger); background: var(--ds-danger-bg); }
.ds-task--ok { border-left-color: var(--ds-accent); }
.ds-task__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-2); }
.ds-task__title { font-size: var(--ds-fs-section); font-weight: 600; }
.ds-task__title a { color: inherit; text-decoration: none; }
.ds-task__title a:hover { text-decoration: underline; }
/* Исход — справа, словом И цветом сразу: цвет один читать нельзя. */
.ds-task__outcome { display: inline-flex; align-items: center; gap: var(--ds-space-1); margin-left: auto; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-task__outcome--warn { color: var(--ds-warn); }
.ds-task__outcome--bad { color: var(--ds-danger); }
.ds-task__outcome--ok { color: var(--ds-accent); }
.ds-task__subject { margin: var(--ds-space-2) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-body); overflow-wrap: anywhere; }
.ds-task__reason { margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-body); }
/* Машинный код длиннее телефона (``clone_step_cabinet_touched_unverified``)
   и без переноса растягивал бы страницу вбок — на мобильном это резало
   ВСЮ вёрстку, а не только строку. */
.ds-task__code { font-family: var(--ds-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: var(--ds-fs-label); color: var(--ds-ink-3); overflow-wrap: anywhere; }
.ds-task__retry { margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-task__foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-3); margin: var(--ds-space-3) 0 0; }
.ds-task__when { font-size: var(--ds-fs-caption); font-variant-numeric: tabular-nums; color: var(--ds-ink-3); }
/* Кнопка «что делать» и подпись под ней стоят вместе и прижаты вправо:
   у строки ровно один следующий шаг, и его не надо искать глазами. */
.ds-task__do { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2); margin-left: auto; }
.ds-task__hint { font-size: var(--ds-fs-label); color: var(--ds-ink-2); }
.ds-task__defect { font-size: var(--ds-fs-caption); color: var(--ds-ink-2); }
.ds-task__meta { margin: var(--ds-space-2) 0 0; }
.ds-task__meta > summary { font-size: var(--ds-fs-label); text-transform: uppercase; letter-spacing: .06em; color: var(--ds-ink-3); cursor: pointer; }
.ds-task__facts { display: grid; grid-template-columns: max-content 1fr; gap: var(--ds-space-1) var(--ds-space-3); margin: var(--ds-space-2) 0 0; font-size: var(--ds-fs-caption); }
.ds-task__facts dt { color: var(--ds-ink-2); }
.ds-task__facts dd { margin: 0; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }

@media (max-width: 760px) {
  .ds-task__outcome, .ds-task__do { margin-left: 0; }
  .ds-task__foot { align-items: flex-start; flex-direction: column; gap: var(--ds-space-2); }
  .ds-task__do { width: 100%; }
  /* Подпись под кнопкой, а не рядом: в один ряд она распирала страницу и
     обрезала на телефоне весь экран, а не себя. */
  .ds-task__hint { flex: 1 0 100%; }
  .ds-taskfilter__field { flex: 1 0 100%; }
}

/* Бейджи работ проекта (заказ B9): «в работе / ждут вас / с ошибкой» в строке
   парка и в шапке проекта. Своей палитры нет — тона задачника берутся
   существующими токенами, дот тот же самый (.ds-dot--warn/--bad). Пилюля
   мельче чипа (.ds-chip): она живёт ВНУТРИ строки таблицы, где рядом уже
   стоят .ds-verdict и .ds-contract-status, и рост высоты строки на 34 px
   ради трёх чисел таблицу бы распух. */
.ds-workbadges { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-1) var(--ds-space-2); }
.ds-workbadge { display: inline-flex; align-items: center; gap: 6px; padding: 2px var(--ds-space-2); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill); background: var(--ds-surface); color: var(--ds-ink-2); font-size: var(--ds-fs-label); line-height: 1.5; text-decoration: none; white-space: nowrap; }
.ds-workbadge strong { color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.ds-workbadge:hover { border-color: var(--ds-ink-2); }
.ds-workbadge--warn { border-color: var(--ds-warn-line); background: var(--ds-warn-bg); color: var(--ds-warn); }
.ds-workbadge--warn strong { color: var(--ds-warn); }
.ds-workbadge--bad { border-color: var(--ds-danger-line); background: var(--ds-danger-bg); color: var(--ds-danger); }
.ds-workbadge--bad strong { color: var(--ds-danger); }
/* В шапке экрана проекта блок стоит отдельной строкой под подзаголовком. */
.ds-stats-head .ds-workbadges { margin-top: var(--ds-space-2); }

/* Строка поиска в шапке (заказ владельца 04.09: «дал номер кампании — а как
   её быстро найти?»). Живёт на каждой странице витрины, поэтому занимает
   ровно столько, сколько нужно строке ввода: разделы слева, пользователь
   справа, поиск между ними и сжимается первым. */
.ds-topbar__search { display: flex; align-items: center; gap: var(--ds-space-2); margin-left: auto; min-width: 0; }
.ds-topbar__search .ds-search { min-width: 200px; flex: 0 1 300px; }
.ds-topbar__search-go { min-height: 38px; padding: 0 var(--ds-space-3); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: transparent; color: var(--ds-ink-2); font: inherit; font-size: 14px; cursor: pointer; }
.ds-topbar__search-go:hover { color: var(--ds-ink); }

/* Страница результатов поиска: одна таблица на все виды совпадений. Свой
   блок, а не ``ds-search``: тем именем зовётся ПОЛЕ ввода, и вторая сущность
   под тем же именем читалась бы как его часть. */
.ds-found__scroll { overflow-x: auto; }
.ds-found__table { width: 100%; border-collapse: collapse; }
.ds-found__table th { text-align: left; padding: var(--ds-space-2) var(--ds-space-3); font-size: var(--ds-fs-caption); color: var(--ds-ink-3); border-bottom: 1px solid var(--ds-line); }
.ds-found__row td { padding: var(--ds-space-2) var(--ds-space-3); border-bottom: 1px solid var(--ds-line); vertical-align: top; }
.ds-found__kind { font-size: var(--ds-fs-caption); color: var(--ds-ink-3); }

/* Блок «Защита флагмана» на карточке кампании. */
.ds-campaign-flagship-control { margin: 18px 0; }
.ds-campaign-flagship-control form { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin-top: 8px; }
.ds-campaign-flagship-mark { margin: 0 0 8px; }

/* На узком экране строка поиска встаёт своей строкой: шапка переносится
   (``flex-wrap``), и зажатое в 34vw поле не годится ни для номера, ни для
   имени. */
@media (max-width: 720px) {
  .ds-topbar__search { flex: 1 0 100%; margin-left: 0; padding-bottom: var(--ds-space-2); }
  .ds-topbar__search .ds-search { flex: 1 1 auto; max-width: none; }
}

/* --- Панель ассистента (docs/42 §3.9) -------------------------------------
   Drawer поверх страницы, а не колонка рядом: панель, отъедающая 400 px,
   ломала бы инвариант §7 (таблица статистики помещается целиком на 1280).
   На узком экране — лист во весь экран: таблицы там и так скроллятся внутри
   своего контейнера. */
.ds-assist-open {
  position: fixed; right: var(--ds-space-4); bottom: var(--ds-space-4);
  z-index: 60; height: var(--ds-control-h); padding: 0 var(--ds-space-4);
  border: 1px solid var(--ds-accent); border-radius: var(--ds-radius-pill);
  background: var(--ds-accent); color: #fff; font: inherit;
  font-size: var(--ds-fs-caption); cursor: pointer; box-shadow: var(--ds-shadow);
}
.ds-assist-open:hover { background: var(--ds-accent-dark); }
.ds-assist {
  position: fixed; top: 0; right: 0; bottom: 0; width: 400px; max-width: 100vw;
  z-index: 70; display: flex; flex-direction: column;
  background: var(--ds-surface); border-left: 1px solid var(--ds-line);
  box-shadow: var(--ds-shadow);
}
.ds-assist[hidden] { display: none; }
.ds-assist__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--ds-space-2); padding: var(--ds-space-3) var(--ds-space-4);
  border-bottom: 1px solid var(--ds-line);
}
.ds-assist__title { display: flex; align-items: center; gap: var(--ds-space-2); flex-wrap: wrap; }
.ds-assist__chip {
  border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill);
  padding: 2px 10px; font-size: var(--ds-fs-caption); color: var(--ds-ink-2);
}
.ds-assist__health { font-size: var(--ds-fs-tiny); }
.ds-assist__health--ok { color: var(--ds-accent); }
/* «Не проверен» — не ошибка, а незнание, и цвет обязан это различать:
   красный ярлык на живом входе гонит владельца чинить то, что цело. */
.ds-assist__health--unknown { color: var(--ds-ink-2); }
.ds-assist__health--bad { color: var(--ds-danger); }
.ds-assist__close {
  border: 0; background: none; font-size: var(--ds-fs-body);
  color: var(--ds-ink-2); cursor: pointer; line-height: 1;
}
.ds-assist__meta {
  padding: var(--ds-space-2) var(--ds-space-4);
  font-size: var(--ds-fs-tiny); color: var(--ds-ink-3);
}
.ds-assist__feed {
  flex: 1 1 auto; overflow-y: auto; padding: 0 var(--ds-space-4) var(--ds-space-3);
  display: flex; flex-direction: column; gap: var(--ds-space-3);
}
.ds-assist__msg { font-size: var(--ds-fs-body); line-height: 1.45; }
.ds-assist__msg--owner .ds-assist__text {
  background: var(--ds-surface-2); border-radius: var(--ds-radius-s);
  padding: var(--ds-space-2) var(--ds-space-3);
}
.ds-assist__msg--system .ds-assist__text {
  background: var(--ds-warn-bg); border: 1px solid var(--ds-warn-line);
  border-radius: var(--ds-radius-s); padding: var(--ds-space-2) var(--ds-space-3);
  font-size: var(--ds-fs-caption);
}
.ds-assist__msg--pending { opacity: .7; }
.ds-assist__who {
  display: block; font-size: var(--ds-fs-label); color: var(--ds-ink-3);
  margin-bottom: 2px;
}
.ds-assist__text { white-space: pre-wrap; word-break: break-word; }
.ds-assist__note { font-size: var(--ds-fs-tiny); color: var(--ds-ink-3); }
.ds-assist__stage {
  padding: var(--ds-space-2) var(--ds-space-4);
  font-size: var(--ds-fs-caption); color: var(--ds-ink-2);
  border-top: 1px solid var(--ds-line);
}
.ds-assist__form {
  border-top: 1px solid var(--ds-line); padding: var(--ds-space-3) var(--ds-space-4);
  display: flex; flex-direction: column; gap: var(--ds-space-2);
}
.ds-assist__input {
  width: 100%; font: inherit; font-size: var(--ds-fs-body); resize: vertical;
  border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s);
  padding: var(--ds-space-2);
}
.ds-assist__actions { display: flex; gap: var(--ds-space-2); flex-wrap: wrap; }
.ds-assist__actions button {
  height: var(--ds-chip-h); padding: 0 var(--ds-space-3); font: inherit;
  font-size: var(--ds-fs-caption); border-radius: var(--ds-radius-s);
  border: 1px solid var(--ds-line-2); background: var(--ds-surface);
  color: var(--ds-ink); cursor: pointer;
}
.ds-assist__send { background: var(--ds-accent); border-color: var(--ds-accent); color: #fff; }
.ds-assist__send:disabled { opacity: .6; cursor: default; }
@media (max-width: 1100px) {
  .ds-assist { width: 100vw; border-left: 0; }
}

/* --- полоса правок кампании (docs/41 §10.2 п.3) --------------------------- */

/* Экран правок собран как ФОРМА, а не как таблица: у каждой строки есть
   «сейчас», поле «станет» и одна серая подсказка человеческим языком.

   Красный на этом экране означает ровно одно — ОПАСНОСТЬ ДЛЯ ДЕНЕГ. Правки
   вверх, расширение показов, снятие даты окончания. Всё остальное — то, что
   надо знать, а не то, чего надо бояться, — набрано серым, а длинные разборы
   спрятаны в «Подробнее». Прежде красным орала каждая доктринальная оговорка
   у каждого поля, и на такой странице красная надпись перестаёт значить
   что-либо у той правки, ради которой она заведена.

   Размер дельты — не вкус вёрстки. Владелец снял отдельную галочку
   подтверждения расхода у денежных правок (§10.1 п.2, §10.2а п.5), и между
   промахом порядком (лишний ноль) и записью в кабинет не осталось ни одного
   машинного рубежа: остались глаза владельца на этой надписи. Поэтому она
   набрана размером карточного числа (--ds-fs-stat-card) и красным цветом
   красной линии. Уменьшить её — снять рубеж. */
.ds-edit-form { margin: 0 0 var(--ds-space-5); }
.ds-edit-structure { margin: 0; }

/* Липкая полоса счёта: сколько строк поедет и чем это подтвердить. Липкая,
   потому что форма длиннее экрана, а ответ на вопрос «сколько я наменял»
   нужен в любой её точке, а не только в конце. */
.ds-edit-bar {
  position: sticky;
  top: 0;
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-2) var(--ds-space-4);
  margin: 0 0 var(--ds-space-4);
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid var(--ds-line);
  border-radius: var(--ds-radius-s);
  background: var(--ds-surface);
  box-shadow: var(--ds-shadow);
}
.ds-edit-bar__count { margin: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-edit-bar__count strong { color: var(--ds-ink); font-size: var(--ds-fs-stat-row); font-variant-numeric: tabular-nums; }
.ds-edit-bar__actions { margin-left: auto; display: flex; flex-wrap: wrap; gap: var(--ds-space-2); }
.ds-edit-bar__actions .ds-button[disabled] { opacity: .5; cursor: default; }

/* Не прочитано из кабинета: отказ, у которого есть кнопка. Тон предупреждения,
   а не ошибки — ничего не сломано, просто нечего показать. */
.ds-edit-unread {
  margin: 0 0 var(--ds-space-4);
  padding: var(--ds-space-3);
  border: 1px solid var(--ds-warn-line);
  border-radius: var(--ds-radius-s);
  background: var(--ds-warn-bg);
}
.ds-edit-unread__words { margin: 0 0 var(--ds-space-2); color: var(--ds-ink); }
.ds-edit-unread__act { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2); }
.ds-edit-unread__hint { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

/* Участок ожидания снимка: сама рамка невидима, в ней живут форма съёма,
   призыв (когда есть что читать нечем) и заметки о ходе сбора. */
.ds-edit-collect { margin: 0 0 var(--ds-space-4); }
.ds-edit-collect__note { margin: var(--ds-space-2) 0 0; color: var(--ds-ink); font-size: var(--ds-fs-caption); }
.ds-edit-collect__note[hidden] { display: none; }
.ds-edit-collect .ds-edit-unread { margin-bottom: 0; }

.ds-edit-section { margin: 0 0 var(--ds-space-5); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-edit-section__unit { color: var(--ds-ink-2); font-weight: 400; font-size: var(--ds-fs-caption); }
.ds-edit-section__lead { margin: 0 0 var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
/* Подпись денег секции: решение принимают здесь, и какое это число — обязано
   стоять здесь же, а не под «Подробнее» (правило двери console/money.py). */
.ds-edit-section__vat { margin: 0 0 var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

.ds-edit-field { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) auto; align-items: center; gap: var(--ds-space-2) var(--ds-space-4); padding: var(--ds-space-3) 0; border-top: 1px solid var(--ds-line); }
.ds-edit-field:first-of-type { border-top: 0; }
/* Тронутая строка. Нетронутые с формы не едут, и владелец обязан видеть, какие
   именно поедут, не сверяя их со своей памятью. */
.ds-edit-field--changed { background: var(--ds-surface-2); box-shadow: inset 3px 0 0 var(--ds-accent); }
.ds-edit-field__label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ds-edit-field__now { display: flex; flex-direction: column; gap: 2px; font-variant-numeric: tabular-nums; }
.ds-edit-field__now-label { color: var(--ds-ink-3); font-size: var(--ds-fs-label); text-transform: lowercase; }
.ds-edit-field__now strong { color: var(--ds-ink); font-size: var(--ds-fs-stat-row); font-weight: 600; overflow-wrap: anywhere; }
.ds-edit-field__applied { color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-edit-field__old { font-variant-numeric: tabular-nums; color: var(--ds-ink-2); }
.ds-edit-field__old strong { color: var(--ds-ink); }
.ds-edit-field__new { display: inline-flex; align-items: center; gap: var(--ds-space-2); }
.ds-edit-field__new input[type="number"] { width: 140px; min-height: var(--ds-control-h); font: inherit; font-variant-numeric: tabular-nums; text-align: right; padding: 0 var(--ds-space-2); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); color: var(--ds-ink); }
.ds-edit-field__new input[type="text"], .ds-edit-field__new input[type="date"], .ds-edit-field__new select { min-height: var(--ds-control-h); font: inherit; padding: 0 var(--ds-space-2); border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-s); background: var(--ds-surface); color: var(--ds-ink); }
.ds-edit-field__new input[type="text"] { width: 100%; min-width: 220px; }
.ds-edit-field__dates { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2) var(--ds-space-3); }
/* Настройки стоят СТОЛБИКОМ, а не в три колонки. У них ввод бывает шириной в
   неделю (168 часов расписания) и длиной в список регионов — в колонку «станет»
   такое не помещается, и трёхколоночная сетка ломала бы строку пополам. */
.ds-edit-field--setting { grid-template-columns: minmax(0, 1fr); }
.ds-edit-field--setting .ds-edit-field__now { flex-direction: row; align-items: baseline; gap: var(--ds-space-2); }
.ds-edit-field--setting .ds-edit-field__now strong { font-size: var(--ds-fs-body); }
.ds-edit-field__unit { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-edit-field__hint, .ds-edit-field__why { grid-column: 1 / -1; margin: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-edit-field__why { color: var(--ds-ink); }
.ds-edit-field__fix { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2); min-height: 44px; }
.ds-edit-field__fix .ds-button--link { min-height: 44px; }
.ds-edit-field__touch { display: inline-flex; align-items: center; gap: var(--ds-space-2); min-height: 44px; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); cursor: pointer; }
.ds-edit-field__touch input { width: 18px; height: 18px; accent-color: var(--ds-accent); }
/* Снятие даты окончания расширяет показы — это единственная галочка настроек,
   которая говорит про деньги, и она одна здесь красная. */
.ds-edit-field__touch--warn { color: var(--ds-danger); font-weight: 600; }
.ds-edit-field__touch--warn input { accent-color: var(--ds-danger); }

/* Длинное объяснение — свёрнуто. Оно нужно, но не каждый раз и не всем сразу. */
.ds-edit-more { grid-column: 1 / -1; margin: var(--ds-space-1) 0 0; }
.ds-edit-more__summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: var(--ds-space-2); min-height: 34px; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-edit-more__summary::-webkit-details-marker { display: none; }
.ds-edit-more__summary::before { content: "ⓘ"; font-size: var(--ds-fs-caption); }
.ds-edit-more__summary:hover { color: var(--ds-ink); }
.ds-edit-more__body { padding: 0 0 var(--ds-space-2) var(--ds-space-4); border-left: 2px solid var(--ds-line); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-edit-more__body p { margin: 0 0 var(--ds-space-1); }
.ds-edit-more--wide { grid-column: 1 / -1; }
/* Наборы корректировок, которых у кампании нет. Ответ «такого набора нет»
   обязателен — но пять пустых рамок подряд и есть та простыня, из-за которой
   на экране не видно, что вообще можно править. */
.ds-edit-empty { margin: 0 0 var(--ds-space-5); padding: var(--ds-space-2) var(--ds-space-3); border: 1px dashed var(--ds-line); border-radius: var(--ds-radius-s); }

/* Предупреждение-дельта: занимает всю ширину строки, чтобы его нельзя было
   пропустить взглядом, скользящим по колонке чисел. */
.ds-edit-delta { grid-column: 1 / -1; margin: var(--ds-space-1) 0 0; font-size: var(--ds-fs-stat-card); font-weight: 700; letter-spacing: -.01em; font-variant-numeric: tabular-nums; color: var(--ds-ink); }
.ds-edit-delta--bad { color: var(--ds-danger); }
.ds-edit-delta--ok { color: var(--ds-accent); }
.ds-edit-delta[hidden] { display: none; }

/* Денежное правило ПАРКА словами. Стоит В ПОТОКЕ секции и над полями, а не под
   «Подробнее»: 11.09 владелец узнал «недельный бюджет ≥ 12 × цена конверсии»
   нажатием «Применить правки» — отказом движка уже после решения. Тон обычного
   текста, а не подсказки: подсказку глаз пропускает, а это условие, при котором
   правка вообще поедет. */
.ds-edit-rule { margin: 0 0 var(--ds-space-3); color: var(--ds-ink); font-size: var(--ds-fs-body); font-weight: 600; font-variant-numeric: tabular-nums; }
/* Пара кампании УЖЕ спорит с правилом — тем же весом, что красная линия денег:
   рамкой, а не одним цветом, потому что цвет теряется среди чисел. */
.ds-edit-rule--bad { padding: var(--ds-space-2) var(--ds-space-3); border: 1px solid var(--ds-danger); border-radius: var(--ds-radius-s); color: var(--ds-danger); }
.ds-edit-rule[hidden] { display: none; }
/* Граница парка у поля — рядом с границами Директа и НЕ ими: техника Директа
   одна на все парки, а эта строка говорит, где откажет движок этого. */
.ds-edit-field__hint--rule { color: var(--ds-ink); font-weight: 600; font-variant-numeric: tabular-nums; }

/* Заготовки показывает САМ их скрипт (`data-delta-ready`), а не класс `js`
   оболочки: класс ставит ВСТРОЕННЫЙ скрипт в <head>, и под строгим
   `script-src 'self'` он не выполняется — заготовки остались бы скрытыми
   ровно там, где внешний файл прекрасно загрузился. Признак готовности
   обязан ставить тот, кто и работает. */
.ds-delta-presets { display: none; }
.ds-delta-presets[data-delta-ready] { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2) var(--ds-space-3); padding: var(--ds-space-3) 0; }
.ds-delta-presets__label { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-delta-presets__button { min-height: 44px; }
.ds-delta-presets__note { flex: 1 1 100%; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-delta-week { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-2) var(--ds-space-3); margin: 0; padding: 0; border: 0; }
.ds-delta-week__day { display: inline-flex; align-items: center; gap: var(--ds-space-2); min-height: 44px; color: var(--ds-ink); font-size: var(--ds-fs-caption); cursor: pointer; }
.ds-delta-week__day input { width: 18px; height: 18px; accent-color: var(--ds-accent); }
/* На узком экране поле правки к копии обязано помещаться ЦЕЛИКОМ вместе со
   своей единицей: при ширине 400px «₽/нед» уезжал за край, и владелец видел
   обрезанное число. Правило узкое — только форма правок к копии: у формы
   правок кампании своя ширина и свой экран. */
@media (max-width: 900px) {
  .ds-chain-plan--delta-form .ds-edit-field__new { width: 100%; }
  .ds-chain-plan--delta-form .ds-edit-field__new input[type="number"] { width: auto; min-width: 0; flex: 1 1 auto; }
  .ds-chain-plan--delta-form .ds-edit-field__new select { min-width: 0; flex: 1 1 auto; }
}
.ds-edit-rows { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--ds-space-3); }
.ds-edit-row { padding: var(--ds-space-3) var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-edit-row--up { border-color: var(--ds-danger-line); background: var(--ds-danger-bg); }
/* Строка, которая этим нажатием не поедет. Приглушена, а не покрашена в
   тревогу: тревога у денег зарезервирована за расширением расхода, и второй
   красный рядом с ним стирал бы разницу между «страшно» и «просто мимо». */
.ds-edit-row--refused, .ds-edit-row--already { background: var(--ds-surface-2); color: var(--ds-ink-2); }
.ds-edit-row__head, .ds-edit-row__pair { margin: 0 0 var(--ds-space-1); }
.ds-edit-row__pair { font-variant-numeric: tabular-nums; }
.ds-edit-row__hint { margin: var(--ds-space-1) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

/* Ход исполнения пакета: единственный экран, отвечающий на вопрос «пошла она
   или нет». Он перезапрашивает сам себя, пока сервер не скажет, что всё
   кончилось. */
.ds-edit-progress { margin: 0 0 var(--ds-space-5); padding: var(--ds-space-4); border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s); background: var(--ds-surface); }
.ds-edit-progress__summary { margin: 0 0 var(--ds-space-3); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-edit-progress__list { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--ds-space-2); }
.ds-edit-progress__item { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: baseline; gap: 2px var(--ds-space-4); padding: var(--ds-space-2) 0; border-top: 1px solid var(--ds-line); }
.ds-edit-progress__item:first-child { border-top: 0; }
.ds-edit-progress__label { min-width: 0; overflow-wrap: anywhere; }
.ds-edit-progress__state { font-weight: 600; }
.ds-edit-progress__detail { grid-column: 1 / -1; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-edit-progress__note { margin: var(--ds-space-3) 0 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

/* Часы показа: неделя × 24 часа. Раскладка нужна не для красоты — без сетки
   это 168 подряд идущих чекбоксов, в которых нельзя найти вечер вторника.
   Час — квадрат размером с палец: попадать по нему приходится на телефоне. */
.ds-edit-schedule { display: grid; gap: var(--ds-space-2); }
.ds-edit-schedule__day { display: flex; flex-wrap: wrap; align-items: center; gap: 2px var(--ds-space-2); }
.ds-edit-schedule__title { flex: 0 0 160px; color: var(--ds-ink); font-size: var(--ds-fs-caption); }
.ds-edit-schedule__hour {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  width: 34px;
  min-height: 40px;
  padding: 2px 0;
  border: 1px solid var(--ds-line);
  border-radius: var(--ds-radius-s);
  background: var(--ds-surface-2);
  color: var(--ds-ink-3);
  font-size: var(--ds-fs-tiny);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.ds-edit-schedule__hour input { width: 14px; height: 14px; margin: 0; accent-color: var(--ds-accent); }
.ds-edit-schedule__hour:has(input:checked) { border-color: var(--ds-accent); background: var(--ds-surface); color: var(--ds-ink); }
/* Час с перенесённым коэффициентом: галочка его НЕ меняет — она гасит и
   зажигает час, а коэффициент переносится как есть. Отметка тона предупреждения
   стоит там, где владелец решает по галочке. */
.ds-edit-schedule__hour--carried { border-color: var(--ds-warn-line); background: var(--ds-warn-bg); color: var(--ds-warn); }

/* Что именно меняется у настройки — построчно. Сводка расписания совпадает с
   обеих сторон при переносе часа, и решение принимают по этому списку. */
.ds-edit-row__changes { margin: var(--ds-space-1) 0 0; padding-left: var(--ds-space-4); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

/* История правок на карточке кампании (docs/41 §2.6). */
.ds-edit-history { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--ds-space-2); }
.ds-edit-history__item { display: grid; gap: 2px; padding: var(--ds-space-2) 0; border-top: 1px solid var(--ds-line); font-variant-numeric: tabular-nums; }
.ds-edit-history__item:first-child { border-top: 0; }
.ds-edit-history__pair--bad { color: var(--ds-danger); }
.ds-edit-history__meta { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
@media (max-width: 900px) {
  .ds-edit-field { grid-template-columns: minmax(0, 1fr); }
  .ds-edit-field__new input[type="number"] { width: 100%; }
  .ds-edit-field__new input[type="text"] { min-width: 0; }
  .ds-edit-field__dates { grid-column: 1 / -1; }
  .ds-edit-bar { position: static; box-shadow: none; }
  .ds-edit-bar__actions { margin-left: 0; width: 100%; }
  .ds-edit-bar__actions .ds-button { flex: 1 1 auto; }
  .ds-edit-progress__item { grid-template-columns: minmax(0, 1fr); }
  /* Пальцем по 34 пикселям не попадают: на узком экране раскрывашки и галочки
     вырастают до пальца, а не остаются мышиными. */
  .ds-edit-more__summary { min-height: 44px; }
  .ds-edit-schedule__title { flex: 1 0 100%; }
}

/* --- Трей действий (docs/46 Ф1) -------------------------------------------
   Полоска внизу КАЖДОГО экрана + выезжающая снизу панель. Своих цветов нет:
   только токены. Трей ниже панели ассистента по z-index и уже её по смыслу —
   ассистент отвечает на вопросы, трей отвечает на нажатия.

   Полоска ФИКСИРОВАНА у нижнего края и НЕ сжимает страницу: инвариант §7
   («таблица статистики помещается на 1280 целиком») держится по ширине, а
   отъеденные снизу 40 px компенсируются отступом у main — иначе последняя
   строка таблицы вечно пряталась бы под полоской. */
/* СКОЛЬКО МЕСТА ТРЕЙ ЗАНИМАЕТ СНИЗУ ПРЯМО СЕЙЧАС — одним числом, и оно
   живое. `--ds-tray-bar-h` — только полоска, она не меняется никогда.
   `--ds-tray-h` — полоска ПЛЮС выехавшая панель; её мерит и печатает скрипт
   (console/static/console/tray.js: publishHeight), потому что высота панели
   зависит от числа строк, от потолка 60vh и от того, открыта ли она вообще.

   Умолчание равно полоске, и это важно: без скрипта (или до первого замера)
   соседи стоят ровно там же, где стояли до этой правки. */
:root { --ds-tray-bar-h: 36px; --ds-tray-h: var(--ds-tray-bar-h); }
:root { --ds-batch-panel-line-h: 56px; --ds-batch-panel-h: var(--ds-batch-panel-line-h); }
.ds-tray {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 55;
  display: flex; flex-direction: column;
  font-size: var(--ds-fs-caption);
}
.ds-tray__bar {
  display: flex; align-items: center; gap: var(--ds-space-2);
  width: 100%; height: var(--ds-tray-bar-h); padding: 0 var(--ds-space-4);
  border: 0; border-top: 1px solid var(--ds-line);
  background: var(--ds-surface-2); color: var(--ds-ink-2);
  font: inherit; font-size: var(--ds-fs-caption); text-align: left;
  cursor: pointer;
}
.ds-tray__bar:hover { background: var(--ds-muted-bg); }
.ds-tray__label { color: var(--ds-ink-3); }
.ds-tray__counts { color: var(--ds-ink); font-variant-numeric: tabular-nums; }
/* «Данные устарели N сек назад» — жёлтым и рядом с числами, а не вместо них:
   старое число остаётся видимым, но перестаёт выдавать себя за свежее. */
.ds-tray__stale { color: var(--ds-warn); }
.ds-tray__stale[hidden] { display: none; }
.ds-tray__caret { margin-left: auto; color: var(--ds-ink-3); }
.ds-tray--open .ds-tray__caret { transform: rotate(180deg); }
/* Живые квитанции подсвечивают саму полоску, а не только числа: владелец
   видит боковым зрением, что работа идёт, не читая строки. */
.ds-tray--active .ds-tray__bar { border-top-color: var(--ds-accent); }
.ds-tray--attention .ds-tray__bar {
  border-top-color: var(--ds-danger); background: var(--ds-danger-bg);
}
@keyframes ds-tray-blink {
  0%   { background: var(--ds-accent); color: #fff; }
  100% { background: var(--ds-surface-2); color: var(--ds-ink-2); }
}
.ds-tray--blink .ds-tray__bar { animation: ds-tray-blink 1.1s ease-out; }

/* Панель выезжает СНИЗУ (мандат владельца 08.09), а не сбоку: сбоку уже
   стоит ассистент, и две панели с одного края спорили бы за один жест. */
.ds-tray__panel {
  order: -1; max-height: min(60vh, 520px); display: flex; flex-direction: column;
  background: var(--ds-surface); border-top: 1px solid var(--ds-line);
  box-shadow: var(--ds-shadow);
}
.ds-tray__panel[hidden] { display: none; }
.ds-tray__head {
  display: flex; align-items: center; gap: var(--ds-space-3);
  padding: var(--ds-space-3) var(--ds-space-4);
  border-bottom: 1px solid var(--ds-line);
}
.ds-tray__title { font-size: var(--ds-fs-section); color: var(--ds-ink); }
.ds-tray__filter {
  display: inline-flex; align-items: center; gap: var(--ds-space-1);
  color: var(--ds-ink-2); cursor: pointer;
}
.ds-tray__close {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  color: var(--ds-ink-2); font-size: var(--ds-fs-body); line-height: 1;
}
.ds-tray__feed { flex: 1 1 auto; overflow-y: auto; }
.ds-tray__list { display: flex; flex-direction: column; }
.ds-tray__empty {
  margin: 0; padding: var(--ds-space-5) var(--ds-space-4);
  color: var(--ds-ink-2);
}
.ds-tray__item { border-bottom: 1px solid var(--ds-line); }
.ds-tray__item[data-flat="1"] > .ds-tray__row { list-style: none; }
.ds-tray__row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: baseline; gap: var(--ds-space-3);
  padding: var(--ds-space-2) var(--ds-space-4); cursor: pointer;
}
/* Крестик «скрыть». Место под него держится ВСЕГДА (четвёртая колонка есть у
   любой строки), иначе метки соседних строк прыгали бы влево-вправо каждый
   раз, когда очередное нажатие доходит до итога. У живого нажатия кнопка
   спрятана: закрыть можно только прочитанное (console/views_tray.py). */
.ds-tray__hide {
  border: 0; background: none; cursor: pointer; padding: 0 2px;
  color: var(--ds-ink-3); line-height: 1; visibility: hidden;
  min-width: 1em; text-align: center;
}
.ds-tray__item[data-can-hide="1"] > .ds-tray__row > .ds-tray__hide {
  visibility: visible;
}
.ds-tray__hide:hover { color: var(--ds-ink); }
.ds-tray__hide[disabled] { opacity: 0.4; cursor: default; }
.ds-tray__row::-webkit-details-marker { display: none; }
.ds-tray__when { color: var(--ds-ink-3); font-variant-numeric: tabular-nums; }
.ds-tray__what { display: flex; gap: var(--ds-space-2); min-width: 0; flex-wrap: wrap; }
.ds-tray__intent { color: var(--ds-ink); }
.ds-tray__subject { color: var(--ds-ink-2); }
a.ds-tray__subject { color: var(--ds-accent); }
/* Метка статуса — ЦВЕТОМ СЕМАНТИКИ, и это единственное место трея, где цвет
   что-то утверждает. Зелёный ставится только на подтверждённом результате:
   «записано, но не подтверждено» красится жёлтым (docs/46 Ф1). */
.ds-tray__badge {
  padding: 2px 10px; border-radius: var(--ds-radius-pill);
  border: 1px solid var(--ds-line-2); background: var(--ds-surface);
  color: var(--ds-ink-2); white-space: nowrap;
}
.ds-tray__badge--ok {
  color: var(--ds-accent); border-color: var(--ds-accent); background: var(--ds-surface);
}
.ds-tray__badge--warn {
  color: var(--ds-warn); border-color: var(--ds-warn-line); background: var(--ds-warn-bg);
}
.ds-tray__badge--bad {
  color: var(--ds-danger); border-color: var(--ds-danger-line); background: var(--ds-danger-bg);
}
.ds-tray__badge--neutral { color: var(--ds-ink-2); }
.ds-tray__body {
  padding: 0 var(--ds-space-4) var(--ds-space-3) var(--ds-space-6);
  display: grid; gap: var(--ds-space-2);
}
.ds-tray__detail { margin: 0; color: var(--ds-ink-2); }
.ds-tray__next {
  margin: 0; padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid var(--ds-warn-line); border-radius: var(--ds-radius-s);
  background: var(--ds-warn-bg); color: var(--ds-ink);
}
/* «Инструмент не знает, что делать» обязано выглядеть иначе, чем указание:
   общая фраза, выданная за инструкцию, хуже молчания (console/codes.py). */
.ds-tray__next--unknown { border-style: dashed; color: var(--ds-ink-2); }
/* Зов стоянки — решение владельца, а не совет: красная рамка отделяет его от
   жёлтого «что делать», которое инструмент даёт сам себе. */
.ds-tray__next--stand {
  border-color: var(--ds-danger-line); background: var(--ds-danger-bg);
}
.ds-tray__inside { margin: 0; padding: 0; list-style: none; display: grid; gap: 2px; }
.ds-tray__inside-row {
  display: flex; justify-content: space-between; gap: var(--ds-space-3);
  color: var(--ds-ink-2);
}
/* Строка кампании, которая ждёт ВАС. Ровно та, что четверо суток говорила
   «идёт»: её обязано быть видно, не читая соседок. */
.ds-tray__inside-row--stands { color: var(--ds-danger); }
.ds-tray__inside-state { color: var(--ds-ink-3); }
.ds-tray__inside-row--stands .ds-tray__inside-state { color: var(--ds-danger); }
.ds-tray__origin { margin: 0; color: var(--ds-ink-3); font-size: var(--ds-fs-tiny); }

/* Тост «Принято, №N». Стоит НАД полоской и левее кнопки ассистента: три
   плавающих элемента у одного угла перекрыли бы друг друга. */
.ds-toast {
  position: fixed; left: 50%; bottom: calc(var(--ds-tray-h) + var(--ds-space-4));
  transform: translateX(-50%); z-index: 80;
  padding: var(--ds-space-3) var(--ds-space-5);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-ink); color: #fff;
  font-size: var(--ds-fs-caption); box-shadow: var(--ds-shadow);
}

/* Полоска съедает 36 px внизу — возвращаем их странице, чтобы последняя
   строка таблицы не пряталась под ней. Отступ БЕЗУСЛОВНЫЙ, а не через
   `body:has(.ds-tray)`: на экране без трея (вход, чужой пользователь) он
   стоит пустой полосой в 36 px и не мешает никому, а вот `:has`, который
   браузер не понял, спрятал бы последнюю строку таблицы под полоской — то
   есть данные. Из двух промахов выбран безвредный. */
body { padding-bottom: var(--ds-tray-bar-h); }
@media (max-width: 900px) {
  .ds-tray__row { grid-template-columns: auto minmax(0, 1fr); }
  .ds-tray__badge { grid-column: 2; justify-self: start; }
  .ds-tray__body { padding-left: var(--ds-space-4); }
}

/* Трей и ассистент делят нижний правый угол, и делить его надо явно.

   Кнопка «Спросить» стоит НАД всем, что занимает трей, а не над одной его
   полоской. Пока здесь было жёсткое 36 px, кнопка поднималась над закрытой
   полоской и ложилась ПОВЕРХ выехавшей панели «Мои нажатия» — закрывая
   собой правый край строк, то есть слово исхода: ровно тот ответ, ради
   которого панель и открывают (претензия владельца 11.09: «кнопка спросить
   перекрывает часть действий и информации в нижней панели»).

   Никакого «z-index побольше»: спор здесь не о том, кто сверху, а о том,
   что два элемента не имеют права занимать одно место. Панель бывает разной
   высоты — от одной строки до потолка 60vh, — поэтому число живое
   (`--ds-tray-h`), и кнопка едет вместе с ним. Отъехать ей всегда есть
   куда: панель не выше 60vh, а кнопке с отступом нужно 56 px.

   Панель ассистента по той же причине заканчивается над треем, а не
   перекрывает его: выехавший разговор не отнимает ответ на «что с моим
   нажатием». Правила дописаны ниже исходных объявлений — ds.css
   дополняется, а не переписывается.

   ПЕРЕЕЗД МГНОВЕННЫЙ, без перехода. Панель вырастает сразу, и кнопка,
   едущая к новому месту хотя бы 120 мс, все эти 120 мс лежит поверх строк —
   то есть закрывает исход ровно тем способом, от которого мы её и убрали.
   Плавность здесь стоила бы дороже, чем стоит. */
.ds-assist-open { bottom: calc(var(--ds-tray-h) + var(--ds-space-4)); }
.ds-assist { bottom: var(--ds-tray-h); }

/* ==========================================================================
   ПАНЕЛЬ ПОДТВЕРЖДЕНИЯ, ВЫЕЗЖАЮЩАЯ СНИЗУ (docs/46 Ф2)
   ==========================================================================
   Мандат владельца, повторённый пять раз: «не нужно переходить на тысячу
   страниц, всё через попапы или выезжающие». Панель — снизу, как и трей: та
   же сторона экрана, тот же жест, одно правило вместо двух.

   Прижата к низу и НЕ по центру: подтверждение — продолжение строки, на
   которую владелец только что смотрел, и список за панелью обязан остаться
   виден. 80vh — потолок из заказа: план перезапуска с дельтой длинный, и
   скроллится он ВНУТРИ панели, а не вместе со страницей.

   Правила дописаны в конец файла: ds.css дополняется, а не переписывается. */
.ds-drawer {
  width: min(1100px, 100vw); max-width: 100vw;
  max-height: 80vh; margin: 0 auto 0 auto;
  /* Прижатие к низу: у <dialog> собственные поля auto по всем сторонам. */
  position: fixed; inset: auto 0 0 0;
  padding: 0; border: 1px solid var(--ds-line-2); border-bottom: 0;
  border-radius: var(--ds-radius) var(--ds-radius) 0 0;
  background: var(--ds-surface); color: var(--ds-ink);
  box-shadow: var(--ds-shadow);
}
.ds-drawer::backdrop { background: rgba(22, 24, 28, .5); }
.ds-drawer[open] { animation: ds-drawer-rise .18s ease-out; }
@keyframes ds-drawer-rise {
  from { transform: translateY(24px); opacity: .4; }
  to   { transform: translateY(0); opacity: 1; }
}
/* Выезд — украшение, и владельцу с чувствительностью к движению он не нужен.
   Содержимое от этого не меняется ни на букву. */
@media (prefers-reduced-motion: reduce) {
  .ds-drawer[open] { animation: none; }
}
.ds-drawer__frame {
  display: flex; flex-direction: column;
  max-height: 80vh;
}
.ds-drawer__head {
  display: flex; align-items: center; gap: var(--ds-space-3);
  padding: var(--ds-space-3) var(--ds-space-5);
  border-bottom: 1px solid var(--ds-line);
}
/* Полоска-хват: та же подсказка «это выехало снизу», что у панели трея. */
.ds-drawer__grip {
  width: 40px; height: 4px; border-radius: var(--ds-radius-pill);
  background: var(--ds-line-2);
}
.ds-drawer__title { margin: 0; font-size: var(--ds-fs-section); }
.ds-drawer__close {
  margin-left: auto; width: 44px; height: 44px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: var(--ds-radius-s);
  background: none; color: var(--ds-ink-2);
  font: inherit; font-size: var(--ds-fs-section); line-height: 1; cursor: pointer;
}
.ds-drawer__close:hover {
  color: var(--ds-ink); border-color: var(--ds-line-2); background: var(--ds-surface-2);
}
/* Полоса состояния обмена. Живёт ОТДЕЛЬНО от тела: тело подменяется каждым
   ответом сервера, а «проверяю, дошло ли» к серверному телу не относится
   вовсе и обязано его пережить. */
.ds-drawer__says:empty { display: none; }
.ds-drawer__say {
  margin: 0; padding: var(--ds-space-3) var(--ds-space-5);
  border-bottom: 1px solid var(--ds-line); color: var(--ds-ink-2);
}
.ds-drawer__say[hidden] { display: none; }
.ds-drawer__say--warn { background: var(--ds-warn-bg); color: var(--ds-ink); }
.ds-drawer__say--bad { background: var(--ds-danger-bg); color: var(--ds-ink); }
.ds-drawer__say--ok { background: var(--ds-surface-2); color: var(--ds-ink); }
.ds-drawer__body {
  flex: 1 1 auto; overflow-y: auto;
  padding: var(--ds-space-5);
}
.ds-drawer .ds-stats-head { margin: 0 0 var(--ds-space-3); }
.ds-drawer .ds-stats-head__title { font-size: var(--ds-fs-section); }
.ds-drawer .ds-stats-table table { min-width: 720px; }

/* Итог нажатия — там же, где его подтверждали. */
.ds-drawer-result__head {
  margin: 0 0 var(--ds-space-3); font-size: var(--ds-fs-section); color: var(--ds-ink);
}
.ds-drawer-result__head--refused { color: var(--ds-danger); }
.ds-drawer-result__lines { margin: 0 0 var(--ds-space-3); padding: 0; list-style: none; display: grid; gap: var(--ds-space-2); }
.ds-drawer-result__line {
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s);
  background: var(--ds-surface-2); color: var(--ds-ink);
}
.ds-drawer-result__line--ok { border-color: var(--ds-accent); }
.ds-drawer-result__line--warn { border-color: var(--ds-warn-line); background: var(--ds-warn-bg); }
.ds-drawer-result__line--bad { border-color: var(--ds-danger-line); background: var(--ds-danger-bg); }
.ds-drawer-result__note { margin: 0 0 var(--ds-space-4); color: var(--ds-ink-2); }
.ds-drawer-result__actions { display: flex; gap: var(--ds-space-3); flex-wrap: wrap; }

/* Без скрипта панели нет, и запасная форма флагмана стоит на карточке
   видимой; со скриптом её место занимает кнопка. Правило то же, что было у
   собственного окна флагмана, — оно и переезжает сюда целиком. */
@media (max-width: 900px) {
  .ds-drawer { max-height: 88vh; }
  .ds-drawer__frame { max-height: 88vh; }
  .ds-drawer__body { padding: var(--ds-space-4); }
  .ds-drawer .ds-stats-table table { min-width: 640px; }
}

/* БЛОК «ГРУППЫ» НА КАРТОЧКЕ КАМПАНИИ (заказ владельца 09.09). Таблица, а не
   список карточек: владелец сравнивает группы между собой — какая работает,
   какая про что и где сколько объявлений, — и сравнивают глазами по столбцам.
   Разворот состава стоит ОТДЕЛЬНОЙ строкой во всю ширину: `<details>` не
   умеет накрывать несколько ячеек, а состав, ужатый в одну колонку, читать
   нечем. */
.ds-groups { margin: var(--ds-space-5) 0; }
.ds-groups__freshness { margin: 0 0 var(--ds-space-2); color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-groups__words { margin: 0 0 var(--ds-space-2); color: var(--ds-ink); }
.ds-groups__hint { color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-groups__act { margin: 0 0 var(--ds-space-2); }
.ds-groups__collect-form { display: none; }
.ds-groups__unread {
  padding: var(--ds-space-3); border: 1px dashed var(--ds-line-2);
  border-radius: var(--ds-radius-s); background: var(--ds-surface-2);
}
.ds-groups__scroll { overflow-x: auto; }
.ds-groups__table { width: 100%; min-width: 860px; border-collapse: collapse; }
.ds-groups__table th, .ds-groups__table td { padding: var(--ds-space-2) var(--ds-space-3); text-align: left; vertical-align: top; }
.ds-groups__table thead th { border-bottom: 1px solid var(--ds-line); color: var(--ds-ink-2); font-size: var(--ds-fs-label); font-weight: 600; }
.ds-groups__group { border-bottom: 1px solid var(--ds-line); }
.ds-groups__row > * { border-top: 1px solid var(--ds-line); }
.ds-groups__name { max-width: 320px; font-weight: 600; }
/* Имя группы — не больше двух строк: у боевых групп имена вида «Плёнка ·
   Екатеринбург · Поиск · широкие», и в одну строку они распирают столбец, а
   без потолка вытягивают строку таблицы на пять строк текста. */
.ds-groups__title {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}
.ds-groups__service {
  display: inline-block; margin: 0 4px 2px 0; padding: 0 7px;
  border: 1px solid var(--ds-line-2); border-radius: var(--ds-radius-pill);
  background: var(--ds-surface-2); color: var(--ds-ink-2);
  font-size: 11px; line-height: 18px; white-space: nowrap;
}
.ds-groups__actions { display: flex; flex-direction: column; align-items: stretch; gap: 4px; min-width: 170px; }
.ds-groups__more > td { padding-top: 0; }
.ds-groups__summary { cursor: pointer; color: var(--ds-ink-2); font-size: var(--ds-fs-label); }
.ds-groups__composition { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: var(--ds-space-4); margin-top: var(--ds-space-2); }
.ds-groups__ads, .ds-groups__keywords { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--ds-space-2); }
.ds-groups__ad {
  display: grid; gap: 2px; padding: var(--ds-space-2);
  border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s);
  background: var(--ds-surface);
}
.ds-groups__ad-head { font-weight: 600; overflow-wrap: anywhere; }
.ds-groups__ad-text { color: var(--ds-ink-2); overflow-wrap: anywhere; }
.ds-groups__ad-meta { display: flex; align-items: center; gap: var(--ds-space-2); flex-wrap: wrap; }
.ds-groups__ad-act { display: flex; gap: var(--ds-space-2); flex-wrap: wrap; }
.ds-groups__ad-act:empty { display: none; }
.ds-groups__keyword { color: var(--ds-ink-2); overflow-wrap: anywhere; }
@media (max-width: 900px) {
  .ds-groups__composition { grid-template-columns: minmax(0, 1fr); }
}

/* --- таблица отбора: прибитая раскладка, липкое имя, карточки на узком --- */
/* Владелец 09.09 дословно: «таблицу нужно горизонтально скролить, визуально
   сложно понять к какой РК относится, столбцы едут».

   Три разные беды в одной фразе, и лечатся они порознь:

   1. «Столбцы едут» — раскладка была автоматической, и ширина колонки
      считалась по её содержимому: строка с длинным вердиктом раздвигала
      столбец, соседняя с коротким — сжимала, и глаз терял вертикаль. Лечится
      table-layout: fixed с явными ширинами: ширина колонки больше не зависит
      от того, что в неё попало.
   2. «Не понять, к какой РК относится» — при горизонтальной прокрутке имя
      уезжало за левый край, и числа оставались без хозяина. Лечится
      прилипанием первого столбца.
   3. Сама прокрутка остаётся — и это нормально: широкая таблица прокручивается,
      а липкое имя держит хозяина чисел.

   Чего лечением НЕ является — вынос столбцов в разворот строки. 09.09 туда
   уехали «Запущена» и «Активность», и владелец 10.09 хватился первой же:
   «куда пропали даты из таблицы когда были запущены РК и фильтрация столбца»,
   а следом — «а ты таблицу не можешь сделать нормальной». Таблица снова
   обычная: все содержательные столбцы стоят в ней, разворот остаётся для
   крупного графика, слов вердикта с порогами и причин погашенных кнопок. */

/* РАСКЛАДКА ПРИБИТА. min-width держит горизонтальную прокрутку осмысленной:
   без него fixed сжал бы колонки до нечитаемого вместо прокрутки.

   САМО ЧИСЛО min-width С 18.09 СТАВИТ СЕРВЕР — inline, в самой разметке
   таблицы (``column_layout.Layout.width``). Состав столбцов настраивается
   (владелец: «столбцы должны быть настраиваемые»), и одно прибитое здесь
   число было бы верным ровно для одной раскладки: у таблицы из пяти столбцов
   min-width на 1534 px растянул бы их на всю ширину впустую, а у таблицы из
   восемнадцати — сжал бы до нечитаемого. Ширины САМИХ столбцов остались тут
   и только тут; сумму по видимым складывает сервер, и совпадение двух мест
   стережёт тест витрины. */
.ds-table--dense .ds-table__grid { table-layout: fixed; min-width: var(--ds-select-table-w, 1424px); }

/* Ширины столбцов — здесь и только здесь, и каждая ПАРНА числу в своде
   ``console/column_layout.py``: свод складывает из них min-width таблицы, и
   разъедься эти два места — прибитая раскладка снова начала бы делить остаток
   на глазок. Раскладка по умолчанию:
   имя 352 + номер 92 + тип 84 + статус 96 + запущена 74 +
   деньги 2×86 + заявки 76 + вердикт 82 + клики 58 + показы 66 + картинка 148 +
   команды 124 = 1424 px.
   Сумма — это МИНИМУМ, а не размер: у таблицы стоит width: 100%, и на широком
   экране прибитая раскладка раздаёт остаток всем столбцам пропорционально —
   таблица занимает экран, а не прокручивается зря.
   Ни одна колонка не «растягивается содержимым»: при table-layout: fixed
   растягиваться нечему, лишнее просто вылезает за край ячейки — так кнопки и
   налезали на соседний столбец, пока у их колонки стояло «width: 1%».
   Город добавляет 88 (итого 1512); кабинет — ещё 96 (итого 1608) и только
   там, где кабинетов в таблице больше одного. 1608 — ЭТО ПОТОЛОК РАСКЛАДКИ ПО
   УМОЛЧАНИЮ, и он выбран под полосу ноутбука 1680: странице там остаётся
   1665 px (вьюпорт минус полоса прокрутки) минус поля 2×16 = 1633. Прокрутка вбок появляется только у
   того, кто добавил столбцов сверх умолчания, — и едет при ней ОБЛАСТЬ
   таблицы, а не страница (правило ниже). */
form[data-selection-page] .ds-table__th--name { width: 352px; min-width: 352px; }
/* НОМЕР И ТИП — СВОИ СТОЛБЦЫ (18.09). Оба факта стояли подписью под именем и
   не умели ни выключаться, ни поворачивать таблицу. Номеру 92 px: девять цифр
   Директа табличными знаками плюс отступы клетки. Типу 84: самое длинное
   слово столбца — «Клики». */
.ds-select-col--number { width: 92px; }
.ds-select-col--type { width: 84px; }
.ds-select-col--state { width: 96px; }
/* Дата dd.mm.yy и «—» встают в 74 px; заголовок «Запущена» — тоже. */
.ds-select-col--started { width: 74px; }
/* Активности шире: в её ячейке стоит не только дата, но и «нет». */
.ds-select-col--seen { width: 78px; }
.ds-select-col--city { width: 88px; }
/* КАБИНЕТ — своя колонка на объединённом экране проекта, где кампании разных
   кабинетов лежат вперемешку. Её нет вовсе там, где кабинет в таблице один:
   она отвечала бы на вопрос, которого нет, и отнимала ширину у графика. */
.ds-select-col--cabinet { width: 96px; }
.ds-table--dense .ds-select-row__cabinet-cell { white-space: normal; overflow-wrap: anywhere; }
.ds-select-col--money { width: 86px; }
.ds-select-col--leads { width: 76px; }
.ds-select-col--verdict { width: 82px; }
/* ОДНА КОЛОНКА — ОДНО ЧИСЛО. Клики и показы стояли двухэтажно в одной ячейке
   («15» крупно, «300» серым под ним), и владелец 18.09 прочёл это как
   задвоение данных: непонятно, что к чему относится. Теперь это два столбца
   со своими заголовками и своими числами в одну строку. */
.ds-select-col--clicks { width: 58px; }
.ds-select-col--shows { width: 66px; }
/* СТОЛБЦЫ ПЛОСКОСТЕЙ «НАСТРОЙКИ РК» И «СОСТОЯНИЕ» (18.09). В раскладку по
   умолчанию они не входят и появляются только там, где владелец их включил, —
   поэтому в сумму 1534 px не входят тоже. Стратегия шире прочих: в ячейке
   стоит русское слово стратегии, а слова эти длинные («Макс. конверсий
   (недельный бюджет)») — клетка их переносит. */
.ds-select-col--strategy { width: 150px; }
.ds-select-col--budget { width: 104px; }
/* ОТРЕЗОК ЦЕН ПЕРЕНОСИТСЯ. У многоцелевых стратегий цена конверсии приходит
   не одним числом, а по цели — и когда цели разошлись, в клетке стоит «890 ₽ –
   1 180 ₽». Прочие клетки этого столбца — обычные числа с ``ds-num``, то есть
   с nowrap: отрезок при нём уехал бы на соседний столбец. Разряды внутри
   числа не рвутся и здесь — их разделяет неразрывный пробел самой двери
   денег. */
.ds-table--dense tbody > tr > td.ds-select-col--budget-range { white-space: normal; }
.ds-select-col--geo { width: 96px; }
.ds-select-col--chain { width: 140px; }
/* ЭФФЕКТ ПЕРЕЗАПУСКА — ОДНО КОРОТКОЕ СЛОВО В ОДНУ СТРОКУ. Переноса здесь нет
   НАМЕРЕННО: клетка плотной таблицы обрезана по своей высоте, и перенесённая
   третья строка уходит под обрезку — «без заметн… измене…» вместо вердикта.
   Поэтому в клетку идёт короткая редакция (``VERDICT_CELL_WORDS``), а полная
   фраза с числами стоит подсказкой той же клетки.

   112 px — ширина самой длинной короткой редакции («без изменений»): 13 знаков
   по 13 px кегля плюс отступы клетки. Уже — и она снова начнёт обрезаться. */
.ds-select-col--effect { width: 112px; }
/* Слово вердикта эффекта: цвет ДОПОЛНЯЕТ слово, а не заменяет его — по цвету
   одному читают те, кто цвет различает, и никто из них не читает по нему, что
   именно сравнивали. «Хуже» спокойно-красное текстом, без плашки: плашкой на
   этих экранах помечают то, что требует решения. */
.ds-select-row__effect { cursor: help; font-weight: 600; }
/* Ночная проба перезапуска: то же слово-с-подсказкой, те же тона, что у
   эффекта (план владельца 04.10). 112 px — «не проверена». */
.ds-select-col--preflight { width: 112px; }
.ds-select-row__preflight { cursor: help; font-weight: 600; }
.ds-select-row__preflight--ok { color: var(--ds-accent); }
.ds-select-row__preflight--bad { color: var(--ds-danger); }
.ds-select-row__preflight--weak { color: var(--ds-danger); opacity: 0.55; font-weight: 500; }
.ds-select-row__preflight--muted { color: var(--ds-ink-3); font-weight: 400; }
.ds-preflight-note { margin: var(--ds-space-2) 0; }
.ds-preflight-note--bad strong { color: var(--ds-danger); }
.ds-preflight-note--ok strong { color: var(--ds-accent); }
.ds-select-row__effect--ok { color: var(--ds-accent); }
.ds-select-row__effect--bad { color: var(--ds-danger); }
.ds-select-row__effect--muted { color: var(--ds-ink-3); font-weight: 400; }
/* Приглушённо-красный: вердикт «хуже» стоит на слишком малом числе заявок,
   чтобы читаться как находка. Направление видно, тревоги нет. */
.ds-select-row__effect--weak { color: var(--ds-danger); opacity: 0.55; font-weight: 500; }
.ds-select-col--touch { width: 150px; }
/* СТОЛБЦЫ ВТОРОЙ ПЛОТНОЙ ТАБЛИЦЫ — сводки кабинетов проекта (18.09, владелец:
   «раскрываешь, и статистика по кабинетам ТОЖЕ в табличном виде»). Ширины
   парны числам ``column_layout.CABINET_COLUMNS``, как и у кампаний; классы
   деланы теми же (``ds-select-col--money``, ``--leads``, ``--clicks``,
   ``--shows``, ``ds-select-row__spark``) — потому таблицы и выглядят
   одинаково. Своих здесь три: имя кабинета, деньги кабинета и число задач.
   Имя уже кампанийного (360 против 384): логин короток, место в клетке
   занимают метки под ним. */
.ds-table__th--cabinet { width: 360px; min-width: 360px; }
/* СТОЛБЦЫ ТРЕТЬЕЙ ПЛОТНОЙ ТАБЛИЦЫ — списка проектов на парке (19.09, владелец:
   «всё должно в одинаковом виде на всех страницах»). Ширины парны числам
   ``column_layout.PROJECT_COLUMNS``; все столбцы, кроме двух, — те же классы,
   что у кабинетов и у кампаний, потому три таблицы и выглядят одной.
   Имя проекта той же ширины, что имя кабинета: под ним стоят те же метки —
   город, вердикт, разбивка по видам кампаний и подпись съёма, — и разъехавшись
   на десяток пикселей, соседние экраны перестали бы выглядеть одинаково. */
.ds-table__th--project { width: 360px; min-width: 360px; }
/* «ЖДЁТ ВАС» — работы проекта своим столбцом. В клетке стоят до трёх бейджей
   («в работе», «ждут вас», «с ошибкой»), каждый со своим адресом; они
   переносятся, потому что это фразы с числами, а не числа: прибитая раскладка
   не растягивает клетку содержимым, и без переноса третий бейдж вылез бы на
   соседний столбец. */
.ds-select-col--awaiting { width: 150px; }
.ds-table--dense .ds-select-col--awaiting { white-space: normal; }
/* Баланс печатается двумя строками («12 300 ₽» и подпись со ставкой и словом
   съёма), и обе обязаны влезть без обрыва. */
.ds-select-col--balance { width: 132px; }
.ds-select-col--tasks { width: 62px; }
.ds-table--dense .ds-select-col--balance { white-space: normal; }
/* Знак цепочки и слово журнала — фразы, а не числа: в прибитой колонке им
   нужен перенос, иначе они вылезут за край ячейки на соседний столбец. */
.ds-table--dense .ds-select-row__chain,
.ds-table--dense .ds-select-row__touch,
.ds-table--dense .ds-select-row__strategy { white-space: normal; overflow-wrap: anywhere; }
/* «За окно не было» — фраза, а не дата: в прибитой колонке ей нужен перенос,
   иначе она вылезет за край ячейки на соседний столбец. Дату перенос не
   трогает — она короче колонки. */
.ds-table--dense .ds-select-row__seen { white-space: normal; }

/* ЛИПКОЕ ИМЯ. Фон обязателен — иначе уезжающие числа просвечивают сквозь
   ячейку, — и он взят inherit у строки: зебра и подсветка наведения красят
   строку, а прилипшая ячейка обязана краситься вместе с ней, иначе первый
   столбец живёт своим цветом.
   Тень справа — признак того, что справа есть ещё: она стоит всегда, потому
   что «есть ли прокрутка прямо сейчас» без скрипта не спросишь, а таблица
   шире экрана на любом ноутбуке. */
.ds-table--dense .ds-select-row__lead-cell {
  position: sticky; left: 0; z-index: 2;
  background: inherit;
  box-shadow: 8px 0 8px -8px rgba(22, 24, 28, .16);
}
/* Шапка тоже прилипает вбок, но НЕ ВЫШЕ липкой панели пакета: поднимись она —
   заголовок «Кампания» наезжал бы на счётчик «Выбрано: N» при вертикальной
   прокрутке, а панель ради того и липнет, чтобы её было видно всегда. Число
   тут ничего не решает: ячейка лежит ВНУТРИ шапки, и наверх её пускает
   z-index самой шапки (3, см. ниже), а не своя двойка. Фон непрозрачный
   собственный, а не inherit строки: наследовать ячейке шапки нечего. */
.ds-table--dense thead .ds-select-row__lead-cell { z-index: 2; background: var(--ds-bg); }

/* ВБОК ЕДЕТ ОБЛАСТЬ ТАБЛИЦЫ, А НЕ СТРАНИЦА. Владелец 18.09 дословно:
   «горизонтальная прокрутка — всё едет криво-косо».

   До этого у области стояло ``overflow-x: visible``, и широкая таблица
   тащила вбок ВСЮ страницу: уезжали шапка витрины, полоса периода, фильтры,
   заголовки разделов — всё, что к таблице отношения не имеет. Теперь
   прокручивается только область: ``overflow-x: auto`` держит содержимое
   внутри, и ``body`` не сдвигается вбок ни на пиксель ни при какой ширине
   таблицы.

   ЦЕНА НАЗВАНА ВСЛУХ: липкой шапки таблицы при этом нет. ``position: sticky``
   считается не от страницы, а от ближайшего предка со своей прокруткой — и
   предком стала сама область (по спецификации CSS Overflow, раз одна ось не
   ``visible``, вторая считается ``auto``); шапка, прилипшая к её верху, при
   вертикальной прокрутке страницы уезжает вместе с областью. Выбор сделан
   владельцем прямо: ровность и «ничего не едет» важнее липкой шапки.

   Прокрутки при этом на ноутбуке 1680 НЕТ ВОВСЕ: раскладка по умолчанию
   (1416 px, с необязательными «Кабинетом» и «Городом» — 1600) в полосу
   страницы влезает целиком. Область едет вбок только у того, кто сам добавил
   столбцов сверх ширины экрана.

   Липкие столбцы ВНУТРИ области работают штатно и остаются: имя слева,
   команды справа (правила ниже). Именно они и были ответом на «не понять, к
   какой РК относится».

   ``scroll-margin-top`` — про программную прокрутку к таблице (якорь, поиск
   на странице, фокус с клавиатуры): над ней липнет шапка витрины (56 px) и
   панель пакета (``--ds-batch-panel-h``), и без отступа браузер подвёл бы
   верх таблицы под обе полосы.

   Только на экране от 901 px: ниже таблицы уже нет — там карточки. */
@media (min-width: 901px) {
  .ds-table--dense .ds-table__scroll {
    overflow-x: auto;
    /* ``overflow-y: hidden``, а не ``auto``: областью со своей прокруткой она
       становится по горизонтали, и всплывашки строк («Подробнее», вердикт,
       меню «⋯») иначе выдавили бы у неё ВТОРУЮ полосу прокрутки — ту самую
       вертикальную, от которой ушли 18.09 («две прокрутки на одном экране»). */
    overflow-y: hidden;
    scroll-margin-top: calc(56px + var(--ds-batch-panel-h));
  }
  /* ВСПЛЫВАШКИ ПОСЛЕДНИХ СТРОК ОТКРЫВАЮТСЯ ВВЕРХ. Область теперь режет по
     своим краям, и всплывашка у нижних строк уходила бы под обрез — а это те
     самые слова, ради которых её открывают (вердикт с порогом, причина
     погашенной кнопки). Четыре нижние строки — запас на самую высокую из
     всплывашек: разворот строки с его списком фактов. */
  .ds-table--dense tbody tr:nth-last-child(-n + 4) .ds-row-more__body,
  .ds-table--dense tbody tr:nth-last-child(-n + 4) .ds-verdict__why,
  .ds-table--dense tbody tr:nth-last-child(-n + 4) .ds-row-menu__list {
    top: auto;
    bottom: 100%;
  }
}

/* ИМЯ — ЦЕЛИКОМ, БЕЗ МНОГОТОЧИЯ. Владелец 18.09: «название кампании должно
   быть видно полностью». До этого имя резалось — сначала двумя строками с
   многоточием, потом одной, — и подпись ссылки была единственным местом, где
   оно стояло целым: имена парка различаются ХВОСТОМ («… · КЛИКИ-2000 · 0708»
   против «… · МАКС-КОНВ-CPA100 · 0608»), и обрезанные они читались как одна и
   та же кампания. Столбец расширен до 384 px и переносит: замер на боевых
   именах — девять из десяти встают в одну-две строки, самые длинные (92–99
   знаков) в три. */
.ds-table--dense .ds-stats-row__name {
  overflow-wrap: anywhere;
}

/* ЗЕБРА И ПОДСВЕТКА. Ответ на «визуально сложно понять, к какой РК
   относится»: полоса ведёт глаз по строке, подсветка подтверждает, где он.
   Красится СТРОКА, а не ячейки: липкая ячейка берёт этот фон через inherit. */
.ds-table--dense tbody .ds-stats-row { background: var(--ds-surface); }
.ds-table--dense tbody .ds-stats-row:nth-child(even) { background: var(--ds-surface-2); }
.ds-table--dense tbody .ds-stats-row:hover { background: var(--ds-muted-bg); }

/* КАРТОЧКИ ВМЕСТО ТАБЛИЦЫ НА УЗКОМ ЭКРАНЕ. Прокручивать таблицу в 1180 px
   пальцем нельзя: владелец теряет и строку, и место, где он был. Поэтому на
   телефоне таблицы нет вовсе — есть карточка кампании: имя со знаками и
   статусом, три числа, ради которых сюда пришли, и кнопки. Картинка по дням,
   город, клики и вердикт остаются в развороте — он работает и здесь. */
@media (max-width: 900px) {
  .ds-table--dense .ds-table__scroll { overflow-x: visible; }
  .ds-table--dense .ds-table__grid { display: block; min-width: 0; table-layout: auto; }
  .ds-table--dense .ds-table__head { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .ds-table--dense tbody { display: block; }
  .ds-table--dense tbody .ds-stats-row {
    display: block; margin: 0 0 var(--ds-space-3); padding: var(--ds-space-3);
    border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s);
  }
  .ds-table--dense tbody .ds-stats-row:nth-child(even) { background: var(--ds-surface); }
  .ds-table--dense tbody td { display: block; padding: 0; border: 0; }
  /* Прилипание на карточке бессмысленно: прокручивать вбок нечего. */
  .ds-table--dense .ds-select-row__lead-cell {
    position: static; box-shadow: none; margin-bottom: var(--ds-space-2);
  }
  /* Три числа в ряд, каждое со своей подписью: без заголовка таблицы
     «700 ₽ · 0 · —» не читается вовсе. */
  .ds-table--dense .ds-select-col--money,
  .ds-table--dense .ds-select-col--leads,
  .ds-table--dense .ds-select-col--number,
  .ds-table--dense .ds-select-col--type,
  .ds-table--dense .ds-select-col--budget,
  .ds-table--dense .ds-select-col--balance,
  .ds-table--dense .ds-select-col--tasks,
  .ds-table--dense .ds-select-col--geo {
    display: inline-block; width: auto; min-width: 92px; margin: 0 var(--ds-space-4) var(--ds-space-2) 0;
    text-align: left;
  }
  /* Номер на карточке — не число в ряду денег, а подпись: выравнивать его по
     правому краю не с чем, соседей-чисел у него тут нет. */
  .ds-table--dense tbody > tr > td.ds-select-col--number { text-align: left; }
  /* КАЖДОЕ ЧИСЛО СО СВОЕЙ ПОДПИСЬЮ. Заголовков на карточке нет, и столбец,
     не названный здесь, приходит на телефон голым значением: «WB_MAXIMUM_
     CONVERSION_RATE / 140 000 ₽ / — / 2 региона» столбиком не читается вовсе.
     Поэтому правило перечисляет ВСЕ столбцы, которые карточка показывает, —
     включая столбцы плоскостей (18.09). Забыть дописать сюда новый столбец
     значит выпустить его на телефон анонимным. */
  .ds-table--dense .ds-select-col--money::before,
  .ds-table--dense .ds-select-col--leads::before,
  .ds-table--dense .ds-select-col--number::before,
  .ds-table--dense .ds-select-col--type::before,
  .ds-table--dense .ds-select-col--state::before,
  .ds-table--dense .ds-select-col--strategy::before,
  .ds-table--dense .ds-select-col--budget::before,
  .ds-table--dense .ds-select-col--geo::before,
  .ds-table--dense .ds-select-col--chain::before,
  .ds-table--dense .ds-select-col--effect::before,
  .ds-table--dense .ds-select-col--preflight::before,
  .ds-table--dense .ds-select-col--balance::before,
  .ds-table--dense .ds-select-col--tasks::before,
  .ds-table--dense .ds-select-col--awaiting::before,
  .ds-table--dense .ds-select-col--touch::before {
    content: attr(data-label); display: block;
    color: var(--ds-ink-3); font-size: var(--ds-fs-label); letter-spacing: .06em; text-transform: uppercase;
  }
  /* Столбцы плоскостей на карточке — блоками во всю ширину: код стратегии
     («PAY_FOR_CONVERSION_MULTIPLE_GOALS») и знак цепочки в колонку по 92 px
     не встают и рвутся посередине слова. */
  .ds-table--dense .ds-select-col--strategy,
  .ds-table--dense .ds-select-col--chain,
  .ds-table--dense .ds-select-col--effect,
  .ds-table--dense .ds-select-col--preflight,
  .ds-table--dense .ds-select-col--awaiting,
  .ds-table--dense .ds-select-col--touch {
    width: auto; margin-bottom: var(--ds-space-2);
  }
  /* Пустая ячейка НА КАРТОЧКЕ НЕ ЗАНИМАЕТ МЕСТА. В таблице пустой столбец —
     это выравнивание, на карточке — подпись без числа под ней: «ЦЕПОЧКА» у
     кампании, которую никто не перезапускал, читалась бы как потерянное
     значение. Пусто здесь и правда значит «нечего сказать»: «не знаем»
     печатается прочерком, а он ячейку не опустошает. */
  .ds-table--dense .ds-select-col--chain:empty,
  .ds-table--dense .ds-select-col--effect:empty,
  .ds-table--dense .ds-select-col--preflight:empty,
  .ds-table--dense .ds-select-col--awaiting:empty,
  .ds-table--dense .ds-select-col--touch:empty { display: none; }
  .ds-table--dense .ds-select-col--state { width: auto; margin-bottom: var(--ds-space-2); }
  /* Картинка по дням, город, клики, вердикт и обе даты — в развороте: на
     375 px им негде стоять, а разворот открывается тем же нажатием. Это
     КАРТОЧКА ТЕЛЕФОНА, а не таблица: на широком экране все эти столбцы стоят
     в таблице, и возвращение «Запущена» с «Активностью» 10.09 карточку не
     трогало — владельца она устраивала. */
  .ds-table--dense .ds-select-row__spark,
  .ds-table--dense .ds-select-col--city,
  .ds-table--dense .ds-select-col--cabinet,
  .ds-table--dense .ds-select-col--clicks,
  .ds-table--dense .ds-select-col--started,
  .ds-table--dense .ds-select-col--seen,
  .ds-table--dense .ds-select-col--verdict { display: none; }
  .ds-table--dense .ds-select-row__action { width: auto; }
  .ds-table--dense .ds-select-row__buttons { flex-direction: row; flex-wrap: wrap; }
  .ds-table--dense .ds-select-row__buttons .ds-button { min-height: 44px; }
}

/* СЕКЦИЯ КАБИНЕТА В ПАКЕТНОМ ПОДТВЕРЖДЕНИИ (docs/46 Ф4). Заголовок —
   <summary>: логин и числа кабинета видны СВЁРНУТЫМИ, потому что свернулись
   подробности, а не смысл. Сворачивать ли, решает сервер (`collapse_cabinets`
   — порог по числу кабинетов), а не эта разметка: у двух-трёх кабинетов
   прятать нечего.

   Правила дописаны в конец файла: ds.css дополняется, а не переписывается. */
.ds-select-group--fold {
  border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s);
  background: var(--ds-surface);
}
.ds-select-group__summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ds-space-3);
  padding: var(--ds-space-3) var(--ds-space-4);
  cursor: pointer; list-style: none;
}
.ds-select-group__summary::-webkit-details-marker { display: none; }
/* Своя стрелка: у <summary> с `list-style: none` маркера нет вовсе, а знак
   «здесь разворачивается» обязан быть виден и без наведения. */
.ds-select-group__summary::before {
  content: "▸"; color: var(--ds-ink-3); font-size: var(--ds-fs-label);
}
.ds-select-group--fold[open] > .ds-select-group__summary::before { content: "▾"; }
.ds-select-group__summary:hover { background: var(--ds-surface-2); }
.ds-select-group__summary:focus-visible {
  outline: 2px solid var(--ds-accent); outline-offset: -2px;
}
.ds-select-group__login { font-weight: 600; color: var(--ds-ink); }
.ds-select-group__meta--bad { color: var(--ds-danger); }
.ds-select-group__fold { padding: 0 var(--ds-space-4) var(--ds-space-4); }
.ds-select-group__link { margin: 0 0 var(--ds-space-2); font-size: var(--ds-fs-caption); }
/* Итоги пакета стоят НАД списком и липкими не делаются: панель скроллится
   целиком, а прибитая шапка в 80vh съела бы у списка последнюю четверть. */
.ds-drawer .ds-select-summary { margin-bottom: var(--ds-space-2); }
@media (max-width: 720px) {
  .ds-select-group__summary { padding: var(--ds-space-3); min-height: 44px; }
  .ds-select-group__fold { padding: 0 var(--ds-space-3) var(--ds-space-3); }
}

/* ========================================================================= */
/* ПЛОТНАЯ СТРОКА ТАБЛИЦЫ КАМПАНИЙ (18.09).

   Владелец открыл объединённый экран проекта и сказал дословно: «это что за
   пиздец лютый», следом — «ближе должно быть к табличному виду, а не карточки
   шириной хуй пойми сколько пространства». Эталон он назвал сам: таблица
   кампаний Яндекс.Директа, где строка — 56 px, в каждой ячейке одно число или
   одно короткое слово в одну строку, заголовки в одну строку, а команды —
   мелкими словами, а не рамочными кнопками.

   Меряется это одним числом: сколько строк видно на экране 1200 px. Было
   четыре (строка 166 px: четыре кнопки столбиком и двухэтажные ячейки), стало
   двадцать одна (строка 49 px). Ничего из данных при этом не убрано — убраны
   повторы: подпись «0 ₽ — это не порча» под каждой нулевой ячейкой, чип «Без
   метки» у каждой строки без города, «с НДС 22 %» в заголовке вместо одной
   строки. Всё это осталось подсказками и разворотом строки.

   Правила дописаны в конец файла: ds.css дополняется, а не переписывается. */

.ds-table--dense .ds-table__grid { width: 100%; }

/* ЗАГОЛОВОК — ОДНА СТРОКА. Двухэтажные шапки («Кликов / показы», «Заявки /
   было → стало») растили высоту шапки и заставляли столбец быть шире слова.
   Что не влезло — в подсказку заголовка (`hint` у _sort_th). */
.ds-table--dense .ds-table__head .ds-table__th {
  padding: 4px 8px 6px;
  font-size: 11px; font-weight: 500; letter-spacing: .02em; text-transform: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-bottom: 1px solid var(--ds-line-2);
  background: var(--ds-bg);
}

/* КЛЕТКА: 6 px сверху и снизу, тонкая черта снизу, содержимое по центру
   высоты. Отступа у клеток этой таблицы не было вовсе (UA-шный 1 px), и всю
   высоту строке задавало содержимое — поэтому она и раздувалась до 166 px. */
.ds-table--dense tbody > tr > td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--ds-line);
  font-size: 13px; line-height: 16px;
  vertical-align: middle;
}
.ds-table--dense tbody > tr > td.ds-num {
  text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.ds-table--dense tbody > tr.ds-select-row--total > td { border-bottom: 2px solid var(--ds-line-2); }

/* ЯЧЕЙКА ИМЕНИ: название целиком и под ним мелкая подпись. Подпись несёт
   ровно то, чего сейчас НЕТ своим столбцом, — номер, вид, кабинет: столбцы
   эти настраиваются, и выключенный столбец не имеет права унести факт из
   строки совсем. Сама подпись по-прежнему в одну строку с многоточием: она
   дополняет имя, а не спорит с ним за высоту. */
.ds-table--dense .ds-select-row__lead-cell .ds-stats-row__lead { align-items: center; gap: 0; }
.ds-table--dense .ds-select-row__ident { flex: 1 1 auto; min-width: 0; }
.ds-table--dense .ds-select-row__title { flex-wrap: nowrap; align-items: baseline; gap: 6px; }
.ds-table--dense .ds-stats-row__name {
  display: block;
  font-size: 13px; font-weight: 500; line-height: 17px;
  white-space: normal; overflow-wrap: anywhere;
}
.ds-table--dense .ds-select-row__sub {
  display: block; color: var(--ds-ink-2); font-size: 11px; line-height: 15px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ds-table--dense .ds-select-row__sub a { color: inherit; }
/* ТОЧКА МЕЖДУ ФАКТАМИ ПОДПИСИ — ПРАВИЛОМ, А НЕ СЛОВОМ В ШАБЛОНЕ. Фактов
   четыре, и каждый бывает выключен (номер и вид ушли в свои столбцы, кабинет
   — в свой): написанный в разметке разделитель рано или поздно выпустил бы
   строку, начинающуюся с точки. Соседний селектор этого не умеет по
   определению — перед ПЕРВЫМ фактом точки нет никогда. */
.ds-table--dense .ds-select-row__subfact + .ds-select-row__subfact::before {
  content: " · ";
}
.ds-table--dense .ds-select-row__sub .ds-select-row__note { color: var(--ds-warn); }
.ds-table--dense .ds-select-row__badge { padding: 0 4px; font-size: 10px; line-height: 14px; }

/* РАЗВОРОТ СТРОКИ ВСПЛЫВАЕТ, А НЕ РАЗДВИГАЕТ. Клетка имени прилипшая, то
   есть позиционированная, — и телу разворота есть от чего отсчитываться.
   Раздвинь он строку, таблица теряла бы место под собой при каждом вопросе
   «а что тут». */
.ds-table--dense .ds-row-more { flex: none; margin: 0 0 0 8px; }
.ds-table--dense .ds-row-more__open { font-size: 11px; white-space: nowrap; }
.ds-table--dense .ds-row-more__body {
  position: absolute; left: 8px; top: 100%; z-index: 6;
  width: 420px; max-width: 78vw; margin: 0;
  white-space: normal; background: var(--ds-surface);
  box-shadow: 0 8px 24px rgba(22, 24, 28, .18);
}

/* СТАТУС И ВЕРДИКТ — СЛОВОМ ВЫСОТОЙ СО СТРОКУ, а не пилюлей с полями. Цвет
   остаётся (по нему читают раньше слова), а рамка и заливка снимаются: в
   таблице на тысячу строк они превращали столбец в светофор и добавляли по
   8 px каждой строке. */
.ds-table--dense .ds-state-chip { padding: 0; border: 0; background: none; font-size: 11px; font-weight: 500; line-height: 15px; }
.ds-table--dense .ds-state-chip--applied { margin-left: 4px; }
.ds-table--dense .ds-select-row__applied-note { display: block; font-size: 10px; line-height: 13px; }
.ds-table--dense .ds-select-row__verdict { position: relative; }
.ds-table--dense .ds-verdict__word {
  padding: 0; border: 0; border-radius: 0; background: none;
  font-size: 12px; font-weight: 500; line-height: 16px;
}
.ds-table--dense .ds-verdict__word--none { color: var(--ds-ink-3); font-weight: 400; }
.ds-table--dense .ds-verdict__why {
  position: absolute; left: 0; top: 100%; z-index: 6;
  width: 320px; max-width: 70vw; padding: var(--ds-space-2);
  border: 1px solid var(--ds-line); border-radius: var(--ds-radius-s);
  background: var(--ds-surface); box-shadow: 0 8px 24px rgba(22, 24, 28, .18);
  white-space: normal;
}

/* Звёздочка вместо фразы. «Последние дни ещё пополнятся» стояло подписью под
   ячейкой заявок и под ячейкой цены В КАЖДОЙ строке — два лишних этажа,
   одинаковых у всех. Смысл остался в подсказке ячейки. */
.ds-table--dense .ds-select-row__maturing { display: inline; margin-left: 2px; color: var(--ds-warn); }

/* Картинка по дням — ровно своей ширины (148 = 146 + 2×1) и без вертикальных
   отступов: это она держит нижнюю границу высоты строки. */
.ds-table--dense .ds-select-row__spark { padding: 2px 1px; }
.ds-table--dense .ds-select-row__spark-head { padding-left: 1px; padding-right: 1px; }

/* Команды — мелкими словами в ряд; «что уже делали» строкой под ними. */
.ds-table--dense .ds-select-row__last { margin: 0; font-size: 11px; line-height: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-table--dense .ds-row-act--main { font-weight: 600; }

@media (max-width: 900px) {
  /* Карточка телефона: та же плотность здесь вредна — ячейки снова
     становятся блоками, и всё, что выше прижато к одной строке, разжимается.
     Перечислено то же, что перечислял старый блок карточек, — иначе правила
     плотности (они стоят ниже по файлу и специфичнее) переехали бы его. */
  .ds-table--dense tbody > tr > td { display: block; padding: 0; border: 0; white-space: normal; font-size: 14px; }
  .ds-table--dense tbody > tr > td.ds-num { text-align: left; }
  .ds-table--dense .ds-select-col--shows { display: none; }
  .ds-table--dense .ds-stats-row__name { white-space: normal; }
  .ds-table--dense .ds-select-row__sub { white-space: normal; }
  .ds-table--dense .ds-row-more__body { position: static; width: auto; max-width: none; margin-top: var(--ds-space-2); }
  .ds-table--dense .ds-verdict__why { position: static; width: auto; max-width: none; }
  /* Палец — 44 px: на карточке слово-команда обязано быть мишенью. */
  .ds-table--dense .ds-row-act { display: inline-flex; align-items: center; min-height: 44px; font-size: 14px; }
}

/* МЕНЮ ВТОРЫХ КОМАНД СТРОКИ. Перезапуск — слово на виду (самое частое
   нажатие), остальное под «⋯»: три слова подряд в столбце команд давали
   строке третий этаж и +11 px КАЖДОЙ строке таблицы. <details> нативный —
   открывается с клавиатуры и с телефона, без строчки скрипта. */
.ds-table--dense .ds-row-menu { position: relative; display: inline-block; }
.ds-table--dense .ds-row-menu > summary { cursor: pointer; list-style: none; color: var(--ds-ink-2); font-size: 13px; line-height: 16px; }
.ds-table--dense .ds-row-menu > summary::-webkit-details-marker { display: none; }
.ds-table--dense .ds-row-menu > summary:hover { color: var(--ds-ink); }
.ds-table--dense .ds-row-menu__list {
  position: absolute; right: 0; top: 100%; z-index: 7;
  display: grid; gap: 6px; min-width: 150px;
  padding: var(--ds-space-2); border: 1px solid var(--ds-line);
  border-radius: var(--ds-radius-s); background: var(--ds-surface);
  box-shadow: 0 8px 24px rgba(22, 24, 28, .18); text-align: left;
}
/* «Подробнее» — знаком, а не словом: слово занимало в ячейке имени полосу
   шириной с само имя, и имена резались там, где резать было нечего. Слово
   осталось подписью для чтеца с экрана и подсказкой ячейки. */
.ds-table--dense .ds-row-more__open { list-style: none; font-size: 13px; line-height: 16px; }
.ds-table--dense .ds-row-more__open::-webkit-details-marker { display: none; }

@media (max-width: 900px) {
  .ds-table--dense .ds-row-menu { display: block; }
  .ds-table--dense .ds-row-menu__list { position: static; box-shadow: none; }
}

/* =========================================================================
   СТРАХОВКА ОТ НАЛОЖЕНИЯ (18.09). На боевом экране «Капельницы» вердикт
   рисовался в ячейке шириной 82 px ФРАЗОЙ в 326 px — «неизвестно: заявки
   считаются не по всем обращениям», — и клики с показами печатались ПОВЕРХ
   неё: два разных факта в одной точке экрана, и ни один не читается.

   При прибитой раскладке (table-layout: fixed) ячейка не растягивается
   содержимым — лишнее просто вылезает наружу, и по умолчанию рисуется. Пока
   каждая такая ячейка затыкалась своим правилом, следующая длинная строка
   находила новую дырку. Поэтому правило общее и на всю таблицу: содержимое
   клетки НЕ ИМЕЕТ ПРАВА покидать клетку.

   Селектор нарочно слабый (`:where(td)` весит ноль): всякое правило, которое
   уже назначило столбцу перенос по слогам (кабинет, стратегия, цепочка,
   журнал), сильнее и остаётся в силе — обрезка им не нужна, им нужен перенос,
   а от наложения их спасает то же `overflow: hidden`. */
.ds-table--dense tbody > tr > :where(td) {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ТРИ КЛЕТКИ ВЫПУСКАЮТ СОДЕРЖИМОЕ НАРУЖУ НАМЕРЕННО — в них живут всплывашки
   («Подробнее», объяснение вердикта, меню «⋯»), и обрезать их значило бы
   срезать половину подсказки. Обрезается у них не клетка, а то, что внутри:
   имя, слово вердикта и подпись последнего действия — каждое своим правилом
   ниже. */
.ds-table--dense tbody > tr > td.ds-select-row__lead-cell,
.ds-table--dense tbody > tr > td.ds-select-row__verdict,
.ds-table--dense tbody > tr > td.ds-select-row__action { overflow: visible; }

/* ВЕРДИКТ — ОДНО СЛОВО В КЛЕТКЕ. Фразу целиком презентер оставил подсказкой
   ячейки и первой строкой раскрытия (``_verdict_cell``); здесь — только
   гарантия, что даже длинное слово («недостаточно данных») режется
   многоточием, а не уезжает на соседний столбец. */
.ds-table--dense .ds-verdict { max-width: 100%; }
.ds-table--dense .ds-verdict__word {
  display: block; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-table--dense .ds-verdict__full { font-weight: 500; color: var(--ds-ink); }

/* Клетка имени выпускает содержимое наружу ради всплывашек (правило выше),
   поэтому длинная строка под именем — «в норме · 1 перестали · 5 затухли ·
   64 не тратят» — без переноса наезжала на столбец «Кликов». Переносится она
   внутри клетки: ширину держит клетка, а не самая длинная фраза. */
.ds-table--dense .ds-select-row__lead-cell .ds-stats-row__lead > span:last-child { min-width: 0; }
.ds-table--dense .ds-select-row__lead-cell .ds-stats-row__status-stack,
.ds-table--dense .ds-select-row__lead-cell .ds-stats-row__status-stack > * { max-width: 100%; }
.ds-table--dense .ds-select-row__lead-cell .ds-verdict__fact { white-space: normal; overflow-wrap: anywhere; }

/* ПЕРВАЯ СТРОКА ЯЧЕЙКИ — ИМЯ. Знак линии стоял здесь же и `flex: none`, а
   текст у него был длиной с имя («поколение 4 из 4 · линия с 07.09.2026»):
   несжимаемый знак забирал всю ширину, и от строки кампании с историей
   перезапусков оставались зелёная пилюля и номер под ней — ИМЕНИ, того, ради
   чего на экран пришли, не было видно вовсе. Знак линии уехал во вторую
   строку ячейки (``sub_badges``), а из первой сжимается ТОЛЬКО имя: оно
   режется многоточием и полностью стоит в подсказке. «МК» остаётся при имени
   и не сжимается — он в два знака шириной, и резать там нечего. */
.ds-table--dense .ds-select-row__title > .ds-stats-row__name {
  flex: 1 1 auto; min-width: 6em;
}
.ds-table--dense .ds-select-row__title > .ds-select-row__badge {
  flex: 0 0 auto; max-width: 5em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-table--dense .ds-select-row__sub .ds-select-row__badge { margin-right: 2px; }

/* КОМАНДЫ ПРИЛИПАЮТ СПРАВА — тем же приёмом, каким имя прилипло слева. Сумма
   ширин видимых столбцов с «Кабинетом» и «Городом» — 1426 px, а полоса
   страницы на ноутбуке 1392: столбец «Что сделать» уезжал за правый край, и
   владелец видел «Перезапуст…» с обрезанной подписью под ним. Прокрутка у
   таблицы есть, но горизонтальная полоса лежит под сотней строк — командой,
   до которой надо доскроллить, пользоваться нельзя.

   Прилипает ТОЛЬКО последний столбец (`:last-child`): владелец вправе
   передвинуть команды в середину, и там прилипший столбец накрывал бы собой
   соседей. Фон непрозрачный и взят у строки — иначе уезжающие под него числа
   просвечивают; тень слева говорит, что слева есть ещё. */
.ds-table--dense tbody > tr > td.ds-select-row__action:last-child,
.ds-table--dense thead > tr > th.ds-select-row__action:last-child {
  position: sticky; right: 0; z-index: 2;
  box-shadow: -8px 0 8px -8px rgba(22, 24, 28, .16);
}
.ds-table--dense tbody > tr > td.ds-select-row__action:last-child { background: inherit; }
.ds-table--dense thead > tr > th.ds-select-row__action:last-child { background: var(--ds-bg); }
/* Подпись «что уже делали» — в одну строку с многоточием и не шире клетки. */
.ds-table--dense .ds-select-row__last { max-width: 100%; }
.ds-table--dense .ds-select-row__buttons { flex-wrap: nowrap; max-width: 100%; }
.ds-table--dense .ds-row-act--main { overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 900px) {
  /* На карточке телефона прилипать некуда и обрезать нечего: клетки — блоки,
     и всё, что выше прижато к одной строке, здесь разворачивается. */
  .ds-table--dense tbody > tr > :where(td) { overflow: visible; white-space: normal; }
  .ds-table--dense tbody > tr > td.ds-select-row__action:last-child {
    position: static; box-shadow: none;
  }
  .ds-table--dense .ds-verdict__word { white-space: normal; }
  .ds-table--dense .ds-select-row__title > .ds-stats-row__name { min-width: 0; }
  /* Кнопки карточки снова встают в несколько рядов: мишень пальца — 44 px,
     и ужимать их в одну строку значило бы вернуть то, от чего ушли. */
  .ds-table--dense .ds-select-row__buttons { flex-wrap: wrap; }
}

/* =========================================================================
   ЭКРАН С ТАБЛИЦЕЙ КАМПАНИЙ ШИРЕ ОСТАЛЬНЫХ (18.09, владелец: «таблица —
   основной инструмент работы, а выглядит небольшой»). Страница витрины
   держит 1440 px — ширина, при которой ЧИТАЕТСЯ ТЕКСТ, и для карточек и
   журналов она верная. Но здесь на странице стоит таблица на восемнадцать
   столбцов: при полосе 1392 px сумма видимых столбцов с «Кабинетом» и
   «Городом» (1426 px) в неё не влезает, и весь запас экрана — 240 px на
   ноутбуке 1680 — уходил в поля по бокам.

   ``max-width`` берётся от РОДИТЕЛЯ (100%), а не от ``100vw``: вьюпорт
   считается вместе с полосой прокрутки, и страница шириной 100vw поехала бы
   вбок целиком. Потолок в 1920 остаётся: на сверхшироком мониторе строка в
   три метра не читается ни одной таблицей.

   Браузер без ``:has()`` просто оставит прежнюю ширину — таблица там
   прокручивается вбок, как и прокручивалась. */
.ds-page:has(form[data-selection-page]) { max-width: min(1920px, 100%); }
/* ПОЛЯ ЭКРАНА С ТАБЛИЦЕЙ УЖЕ ОБЫЧНЫХ — 16 вместо 24. Шестнадцать пикселей с
   каждой стороны решают, влезает ли раскладка по умолчанию в ноутбучные 1680
   целиком или таблица начинает ехать вбок; текста во всю ширину на этом
   экране нет — есть таблица, которой поля только мешают. */
.ds-page:has(form[data-selection-page]) { padding-left: var(--ds-space-4); padding-right: var(--ds-space-4); }
/* Полоса вопроса растянута отрицательными полями под поля страницы — а они
   на этом экране свои. Не поправь её, она вылезла бы за край на 8 px с
   каждой стороны. */
.ds-page:has(form[data-selection-page]) .ds-askbar {
  margin-left: calc(-1 * var(--ds-space-4));
  margin-right: calc(-1 * var(--ds-space-4));
  padding-left: var(--ds-space-4);
  padding-right: var(--ds-space-4);
}

/* Метка города — чип, а не текст клетки: обрезку клетки он не наследует и в
   столбце 88 px обрывался без многоточия («Санкт-Петербу»). Многоточие тут
   не украшение — оно единственный признак, что слово оборвано, а не такое. */
.ds-table--dense .ds-stats-row__city {
  display: inline-block; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; vertical-align: bottom;
}

/* РАСКРЫТАЯ КЛЕТКА ПОДНИМАЕТСЯ НАД СОСЕДЯМИ. Прилипшие клетки (имя слева,
   команды справа) стоят на z-index 2 и потому создают свой слой: всплывашка
   внутри них — «Подробнее», меню «⋯» — считается уже внутри этого слоя, и
   прилипшие клетки строк НИЖЕ рисуются поверх неё. Раскрытая клетка на время
   раскрытия поднимается выше их всех.

   Правило безвредно там, где клетка не позиционирована: z-index у обычной
   клетки не действует вовсе. */
.ds-table--dense tbody > tr > td:has(details[open]) { z-index: 8; }

/* =========================================================================
   ЭКРАН ПРОЕКТА ПЕРЕВЁРНУТ: ТАБЛИЦА — ПЕРВЫМ ЭКРАНОМ (18.09)

   Владелец дословно: «таблица — основной рабочий инструмент, его нужно при
   каждом действии прокручивать вниз чтобы добраться; фильтры хуй знает где,
   таблица хуй знает где».

   Лечится это тремя правилами ниже и ничем больше: порядком блоков (разметка),
   свёрнутыми блоками под таблицей (``.ds-fold``) и плотностью того, что над
   ней осталось (полоса чисел и полоса чипов). Ни один блок с экрана не убран.
   ========================================================================= */

/* СВЁРНУТЫЙ БЛОК. Нативный <details>: открывается без скрипта, с клавиатуры и
   с телефона, и содержимое его ОСТАЁТСЯ В РАЗМЕТКЕ — поиск по странице и
   стражи разметки видят его так же, как видели раскрытым.

   Состояние намеренно не запоминается: владелец приходит на экран ради
   таблицы, и блок, оставшийся открытым со вчера, снова унёс бы её вниз. */
.ds-fold {
  margin: 0 0 var(--ds-space-3);
  border: 1px solid var(--ds-line);
  border-radius: var(--ds-radius-2);
  background: var(--ds-surface);
}
.ds-fold__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ds-space-2);
  padding: var(--ds-space-3) var(--ds-space-4);
  font-size: var(--ds-fs-section);
  font-weight: 600;
  cursor: pointer;
}
.ds-fold__summary::marker { color: var(--ds-ink-3); }
.ds-fold__summary:hover { color: var(--ds-accent); }
/* Число в заголовке — чтобы свёрнутый блок отвечал на свой вопрос ещё
   свёрнутым: «Кабинеты · 12» значит, что за ним двенадцать строк, а не что
   там пусто. */
.ds-fold__count { color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-weight: 400; }
.ds-fold[open] > .ds-fold__summary { border-bottom: 1px solid var(--ds-line); }
.ds-fold__body { padding: var(--ds-space-4); }
/* Внутренние блоки уже несут свои поля — двойной рамки и двойного отступа тут
   быть не должно. */
.ds-fold__body > section:last-child,
.ds-fold__body > .ds-table:last-child { margin-bottom: 0; }

/* ЧТО ДАЛИ ПЕРЕЗАПУСКИ ПРОЕКТА. Главная новость — в заголовке свёрнутого
   блока («12 пар · лучше 5 · хуже 1»), потому что ради неё блок и открывают;
   внутри — те же числа врозь, разбор несудимых по причинам и правило, по
   которому вердикт поставлен. Своей плиткой в «Итогах периода» это стоять не
   может: там числа за ВЫБРАННОЕ окно, а окна сравнения выбрал сам расчёт. */
.ds-restart-effects__counts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-3) var(--ds-space-5);
  margin: 0 0 var(--ds-space-4);
  padding: 0;
  list-style: none;
}
.ds-restart-effects__count { display: flex; align-items: baseline; gap: var(--ds-space-2); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }
.ds-restart-effects__count strong { color: var(--ds-ink); font-size: var(--ds-fs-stat-cabinet); font-variant-numeric: tabular-nums; }
.ds-restart-effects__count--ok strong { color: var(--ds-accent-dark); }
.ds-restart-effects__count--bad strong { color: var(--ds-danger); }
.ds-restart-effects__count--weak strong { color: var(--ds-danger); opacity: 0.55; }
.ds-restart-effects__count--muted strong { color: var(--ds-ink-3); }
.ds-restart-effects__title { margin: 0 0 var(--ds-space-2); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); font-weight: 600; }
.ds-restart-effects__reasons { margin: 0 0 var(--ds-space-4); padding: 0 0 0 var(--ds-space-4); color: var(--ds-ink-2); font-size: var(--ds-fs-caption); line-height: 1.6; }
.ds-restart-effects__reason strong { color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.ds-restart-effects__basis { margin: 0 0 var(--ds-space-2); color: var(--ds-ink-3); font-size: var(--ds-fs-caption); line-height: 1.6; }
.ds-restart-effects__basis:last-child { margin-bottom: 0; }

/* ПОЛОСА КЛЮЧЕВЫХ ЧИСЕЛ — ОДНОЙ СТРОКОЙ, А НЕ ПЛИТАМИ. Владелец: «4–5
   ключевых чисел одной строкой, компактно, не карточками-плитами». Числа и
   подписи те же, что были в плитах (ни одно не пересчитано) — изменились
   раскладка и кегль: шесть колонок вместо четырёх, рамка одна на всю полосу,
   а не на каждое число. */
.ds-project-focus {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  grid-template-columns: none;
  margin-bottom: var(--ds-space-3);
  padding: var(--ds-space-2) 0;
  border: 1px solid var(--ds-line);
  border-radius: var(--ds-radius);
  background: var(--ds-surface);
}
.ds-project-focus article {
  flex: 1 1 140px;
  min-width: 0;
  padding: 0 var(--ds-space-4);
  border: 0;
  border-left: 1px solid var(--ds-line);
  border-radius: 0;
  background: none;
}
.ds-project-focus article:first-child { border-left: 0; }
.ds-project-focus span { font-size: var(--ds-fs-label); }
.ds-project-focus strong { margin-top: 0; font-size: var(--ds-fs-section); line-height: 1.2; font-variant-numeric: tabular-nums; }
.ds-project-focus small { color: var(--ds-ink-3); font-size: 10px; line-height: 13px; }

/* ПОЛОСА ЧИПОВ — ОДИН РЯД НА ТРИ ГРУППЫ. Преднастройки, кабинеты и «что
   делала за окно» стояли тремя секциями подряд, каждая со своим
   заголовком-строкой; вместе они занимали три этажа над таблицей. Теперь
   заголовок каждой группы — подпись слева от её чипов, а группы переносятся
   как обычный текст. */
.ds-select-chipbar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ds-space-2) var(--ds-space-5);
  margin: 0 0 var(--ds-space-2);
}
.ds-select-chipbar > section {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ds-space-2);
  min-width: 0;
  margin: 0;
}
.ds-select-chipbar .ds-section-title {
  margin: 0;
  color: var(--ds-ink-2);
  font-size: var(--ds-fs-caption);
  font-weight: 500;
}
.ds-select-chipbar .ds-select-presets__pinned,
.ds-select-chipbar .ds-select-cabinets__list,
.ds-select-chipbar .ds-select-activity__list { margin: 0; }
.ds-select-chipbar .ds-select-presets__all { margin: 0; }
/* Пояснения групп — целой строкой под полосой, а не внутри ряда: втиснутые
   между чипами, они разрывали ряд посередине. */
.ds-select-chipbar .ds-select-presets__hint { flex: 1 1 100%; margin: 0; }

/* ФИЛЬТРЫ — ТАМ ЖЕ, ГДЕ ЧИПЫ, И НЕ ШИРЕ СВОЕЙ СТРОКИ. Раскрывашка свёрнута
   стоит одной строкой; раскрытая — разворачивается на всю ширину, как и
   раньше. */
.ds-select-filters { margin: 0 0 var(--ds-space-2); }
.ds-select-filters__open { padding: var(--ds-space-2) var(--ds-space-3); }
.ds-select-summary { margin: 0 0 var(--ds-space-2); }
.ds-select-toolbar { margin: 0 0 var(--ds-space-2); }

@media (max-width: 900px) {
  /* На телефоне полоса чисел снова становится столбиком: шесть чисел в ряд
     на 375 px нечитаемы. */
  .ds-project-focus { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ds-space-2); padding: var(--ds-space-3); }
  .ds-project-focus article { padding: 0; border-left: 0; }
  .ds-fold__body { padding: var(--ds-space-3); }
}

/* Заголовок раздела над таблицей — ОДНОЙ СТРОКОЙ и без воздуха сверху: на
   экране проекта над ним уже стоят полоса окна и полоса чисел, и обычные
   24 px отступа уносили таблицу ещё ниже. */
.ds-section-bar--tight { margin: 0 0 var(--ds-space-2); }
.ds-section-bar--tight .ds-section-title { font-size: var(--ds-fs-body); }
.ds-section-bar--tight .ds-section-note { margin: 0; color: var(--ds-ink-2); font-size: var(--ds-fs-caption); }

/* ПЛОТНОСТЬ ТОГО, ЧТО ОСТАЛОСЬ НАД ТАБЛИЦЕЙ. Каждая полоса над таблицей —
   это пиксели, на которые владелец прокручивает при КАЖДОМ действии. Здесь
   сняты только поля и воздух: ни один блок не убран и ни одно слово не
   спрятано. */
.ds-page:has(form[data-selection-page]) .ds-stats-head { margin-bottom: var(--ds-space-3); }
.ds-page:has(form[data-selection-page]) .ds-stats-head__collection { margin-top: var(--ds-space-1); }
.ds-page:has(form[data-selection-page]) .ds-period { margin-bottom: var(--ds-space-2); gap: var(--ds-space-2) var(--ds-space-3); }
.ds-page:has(form[data-selection-page]) .ds-period__field input { min-height: 32px; }
.ds-page:has(form[data-selection-page]) .ds-period .ds-button { min-height: 32px; }
.ds-page:has(form[data-selection-page]) .ds-project-focus { margin-bottom: var(--ds-space-2); }
.ds-page:has(form[data-selection-page]) .ds-fold { margin-bottom: var(--ds-space-2); }
.ds-page:has(form[data-selection-page]) .ds-fold__summary { padding: var(--ds-space-2) var(--ds-space-3); font-size: var(--ds-fs-body); }
.ds-campaign-batch { margin: 0 0 var(--ds-space-2); padding: 6px 10px; }
.ds-page:has(form[data-selection-page]) .ds-select-summary { margin-bottom: var(--ds-space-1); }
.ds-page:has(form[data-selection-page]) .ds-select-toolbar { margin-bottom: var(--ds-space-1); }
.ds-page:has(form[data-selection-page]) .ds-select-chipbar { gap: var(--ds-space-1) var(--ds-space-5); }
.ds-page:has(form[data-selection-page]) .ds-project-focus { padding: var(--ds-space-1) 0; }
.ds-page:has(form[data-selection-page]) .ds-project-focus strong { font-size: var(--ds-fs-body); font-weight: 600; }
/* Кнопки пакета — мельче: восемь подписей в два ряда стоили таблице сотню
   пикселей высоты ровно над ней. Мишень пальца (44 px) остаётся на телефоне —
   там своё правило. */
.ds-campaign-batch .ds-button--row { padding: 3px 8px; font-size: var(--ds-fs-caption); }

/* ЭКРАН ПАРКА — ТОЙ ЖЕ ШИРИНЫ, ЧТО ЭКРАНЫ С ТАБЛИЦЕЙ КАМПАНИЙ (19.09). Правило
   выше опознавало широкий экран по форме отбора (``form[data-selection-page]``),
   и парк под него не подходил: формы отбора у него нет, а плотная таблица есть.
   Владелец 19.09: «всё должно в одинаковом виде на всех страницах» — а страница
   со списком проектов оставалась на 270 px уже соседних, и одни и те же строки
   на двух экранах стояли в колонках разной ширины.

   Признаком широкого экрана стала САМА ПЛОТНАЯ ТАБЛИЦА: она и есть причина, по
   которой странице нужен весь экран. Числа те же, что у правила выше, — второй
   ширины экрана витрины не заводится.

   Браузер без ``:has()`` просто оставит прежнюю ширину: таблица там
   прокручивается вбок, как и прокручивалась. */
.ds-page:has(.ds-table--dense) { max-width: min(1920px, 100%); }
.ds-page:has(.ds-table--dense) { padding-left: var(--ds-space-4); padding-right: var(--ds-space-4); }

/* СОСТАВ ПАРКА — та же плотная таблица, строка на кабинет. Ширины в сумме
   равны PASSPORT_TABLE_WIDTH (views_passport): сумма уходит в min-width
   таблицы, и разъехавшись, они снова делили бы остаток на глазок. */
.ds-passport-filter { margin-bottom: var(--ds-space-3); }
.ds-passport-filter select { min-height: 32px; max-width: 260px; }
.ds-table__grid--passport .ds-passport__login { width: 230px; }
.ds-table__grid--passport .ds-passport__site { width: 170px; }
.ds-table__grid--passport .ds-passport__counter { width: 120px; }
.ds-table__grid--passport .ds-passport__tracker { width: 130px; }
.ds-table__grid--passport .ds-passport__reps { width: 180px; }
.ds-table__grid--passport .ds-passport__elama { width: 210px; }
.ds-table__grid--passport .ds-passport__tokens { width: 150px; }
.ds-table__grid--passport .ds-passport__checked { width: 150px; }
.ds-passport__main,
.ds-passport__sub { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-passport__main { color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.ds-passport__login .ds-passport__main { font-weight: 600; }
.ds-passport__sub { margin-top: 2px; color: var(--ds-ink-2); font-size: var(--ds-fs-tiny); line-height: 14px; }
.ds-passport__caption { color: var(--ds-ink-2); font-size: var(--ds-fs-tiny); font-weight: 400; }
.ds-passport__quiet { color: var(--ds-ink-3); }
.ds-passport__reps .ds-select-row__badge + .ds-select-row__badge { margin-left: 4px; }
.ds-passport__sub.ds-passport__logins { display: flex; gap: 4px; }
.ds-passport__names { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-passport__more { flex: none; font-variant-numeric: tabular-nums; }
.ds-passport__link { color: var(--ds-accent); text-decoration: none; }
.ds-passport__link:hover { text-decoration: underline; }
.ds-table__grid--passport .ds-select-row__badge { display: inline-block; cursor: default; }
.ds-table__grid--passport .ds-select-row__badge[title] { cursor: help; }
.ds-passport__tokens .ds-select-row__badge + .ds-select-row__badge { margin-left: 4px; }
.ds-passport__checked .ds-select-row__badge { margin-top: 2px; }
.ds-passport__off { color: var(--ds-ink-3); background: var(--ds-surface); }
.ds-passport__group > th {
  padding: 14px 8px 6px;
  border-bottom: 1px solid var(--ds-line-2);
  background: var(--ds-bg);
  text-align: left;
  font-size: var(--ds-fs-caption);
  font-weight: 400;
}
.ds-passport__group-name { color: var(--ds-ink); font-weight: 600; }
.ds-passport__group-count { color: var(--ds-ink-2); font-variant-numeric: tabular-nums; }
.ds-passport__group-blocked { margin-left: 6px; color: var(--ds-danger); font-size: var(--ds-fs-tiny); }
.ds-passport__login .ds-passport__blocked { margin-top: 2px; }
.ds-passport__row--blocked > td:not(.ds-passport__login) { opacity: .55; }
@media (max-width: 900px) {
  .ds-table--dense .ds-passport__group { display: block; }
  .ds-table--dense .ds-passport__group > th { display: block; padding: var(--ds-space-3) 0 var(--ds-space-2); border: 0; }
  .ds-table__grid--passport tbody td { width: auto; margin-bottom: var(--ds-space-1); white-space: normal; }
  .ds-table__grid--passport tbody td[data-label]::before {
    content: attr(data-label); display: block; color: var(--ds-ink-2); font-size: var(--ds-fs-tiny);
  }
}
