/* TOOLS-GO Design System v3 — components
   Требует tokens.css. Классы с префиксом tg-. Заменяет .pwa-* из pwa_base.css. */

*, *::before, *::after { box-sizing: border-box; }

html { font-size: 16px; -webkit-font-smoothing: antialiased; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--fg);
  background: var(--canvas);
  font-feature-settings: 'tnum' 0;
}

a { color: var(--a-500); text-decoration: none; }
a:hover { color: var(--a-600); text-decoration: underline; text-underline-offset: 2px; }

/* ============ Типографика ============ */
.tg-page-title { font-size: var(--fs-title); line-height: var(--lh-title); font-weight: 600; letter-spacing: var(--tracking-title); margin: 0; }
.tg-section-title { font-size: var(--fs-strong); line-height: var(--lh-strong); font-weight: 600; margin: 0; }
.tg-label {
  font-family: var(--font-cond);
  font-size: var(--fs-micro); line-height: var(--lh-micro);
  font-weight: 600; letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-muted);
}
.tg-meta { font-size: var(--fs-meta); line-height: var(--lh-meta); color: var(--fg-muted); }
/* nowrap по той же причине, что и у .tg-td--num: «19 524 648» разрывается по
   разрядам и читается как два разных числа. Там, где перенос нужен (длинный
   e-mail, набранный тем же моноширинным), шаблон снимает это сам. */
.tg-num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-num); white-space: nowrap; }
.tg-metric { font-family: var(--font-mono); font-size: var(--fs-metric); line-height: var(--lh-metric); font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-num); }
.tg-code {
  font-family: var(--font-mono); font-size: var(--fs-meta);
  background: var(--surface-sunken); border: 1px solid var(--hairline);
  border-radius: var(--r-1); padding: 1px var(--s-3);
}
.tg-neg { color: var(--err-fg); }
.tg-pos { color: var(--ok-fg); }

/* ============ Кнопки ============ */
.tg-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-3);
  height: var(--ctl-md); padding: 0 var(--s-5);
  font-family: var(--font-sans); font-size: var(--fs-body); font-weight: 500; line-height: 1;
  border: 1px solid transparent; border-radius: var(--r-2);
  background: none; color: var(--fg); cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
  white-space: nowrap;
}
.tg-btn:focus-visible { outline: none; box-shadow: var(--ring); }
.tg-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.tg-btn--primary { background: var(--a-500); color: var(--fg-on-accent); }
.tg-btn--primary:hover:not(:disabled) { background: var(--a-600); }
.tg-btn--primary:active:not(:disabled) { background: var(--a-700); }
.tg-btn--secondary { background: var(--surface); border-color: var(--border); color: var(--fg); }
.tg-btn--secondary:hover:not(:disabled) { background: var(--surface-hover); border-color: var(--border-strong); }
.tg-btn--ghost { color: var(--fg-secondary); }
.tg-btn--ghost:hover:not(:disabled) { background: var(--surface-hover); color: var(--fg); }
.tg-btn--danger { background: var(--surface); border-color: var(--err-bd); color: var(--err-fg); }
.tg-btn--danger:hover:not(:disabled) { background: var(--err-bg); }
.tg-btn--sm { height: var(--ctl-sm); padding: 0 var(--s-4); font-size: var(--fs-meta); }
.tg-btn--lg { height: var(--ctl-lg); padding: 0 var(--s-6); font-size: var(--fs-strong); }
.tg-btn--icon { width: var(--ctl-md); padding: 0; }
/* Квадрат приходится задавать явно: ширину кнопка-иконка берёт из --ctl-md, а
   высоту — из размерного модификатора. На тач-экране --ctl-md становится 44px,
   тогда как --ctl-sm остаётся мелким, и без этих двух строк все кнопки действий
   в строках таблиц и в мобильных карточках расплывались в прямоугольник 44×26. */
.tg-btn--icon.tg-btn--sm { width: var(--ctl-sm); }
.tg-btn--icon.tg-btn--lg { width: var(--ctl-lg); }
.tg-btn--block { width: 100%; }
.tg-btn__kbd { font-family: var(--font-mono); font-size: var(--fs-micro); opacity: 0.7; }

.tg-btn-group { display: inline-flex; }
.tg-btn-group > .tg-btn { border-radius: 0; border-color: var(--border); background: var(--surface); }
.tg-btn-group > .tg-btn:first-child { border-radius: var(--r-2) 0 0 var(--r-2); }
.tg-btn-group > .tg-btn:last-child { border-radius: 0 var(--r-2) var(--r-2) 0; }
.tg-btn-group > .tg-btn + .tg-btn { margin-left: -1px; }
.tg-btn-group > .tg-btn[aria-pressed='true'] { background: var(--a-tint-strong); color: var(--a-500); border-color: var(--a-300); z-index: 1; }

/* ============ Поля и формы ============ */
.tg-field { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-3); min-width: 0; }
.tg-field__label { font-size: var(--fs-meta); font-weight: 500; color: var(--fg-secondary); }
.tg-field__label--required::after { content: '*'; color: var(--err-fg); margin-left: 2px; }
.tg-field__hint { font-size: var(--fs-micro); color: var(--fg-faint); }
.tg-field__error { font-size: var(--fs-micro); color: var(--err-fg); }

.tg-input, .tg-select, .tg-textarea {
  width: 100%; min-width: 0;
  height: var(--ctl-md); padding: 0 var(--s-4);
  font-family: var(--font-sans); font-size: var(--fs-body); color: var(--fg);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-2);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
  -webkit-appearance: none; appearance: none;
}
.tg-textarea { height: auto; min-height: 72px; padding: var(--s-4); line-height: var(--lh-body); resize: vertical; }
.tg-input::placeholder, .tg-textarea::placeholder { color: var(--fg-faint); }
.tg-input:hover, .tg-select:hover, .tg-textarea:hover { border-color: var(--border-strong); }
.tg-input:focus, .tg-select:focus, .tg-textarea:focus { outline: none; border-color: var(--border-focus); box-shadow: var(--ring); }
.tg-input:disabled, .tg-select:disabled, .tg-textarea:disabled { background: var(--surface-sunken); color: var(--fg-faint); cursor: not-allowed; }
.tg-input[aria-invalid='true'] { border-color: var(--err-fg); }
.tg-input[aria-invalid='true']:focus { box-shadow: var(--ring-err); }
.tg-input--num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }
.tg-select {
  padding-right: var(--s-9);
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 17px) 14px, calc(100% - 12px) 14px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  color: var(--fg);
}
.tg-input-group { position: relative; display: flex; align-items: center; min-width: 0; }
.tg-input-group__icon { position: absolute; left: var(--s-4); width: 14px; height: 14px; color: var(--fg-faint); pointer-events: none; }
.tg-input-group__icon ~ .tg-input { padding-left: var(--s-9); }
.tg-input-group__suffix { position: absolute; right: var(--s-4); font-family: var(--font-mono); font-size: var(--fs-micro); color: var(--fg-faint); }
/* Действие внутри поля: показать пароль, очистить, открыть календарь.
   Отступ полю даёт :has(), а не соседний селектор: кнопка в разметке стоит
   после input, и `~` до неё не дотягивается. */
.tg-input-group__action { position: absolute; right: var(--s-2); }
.tg-input-group:has(.tg-input-group__action) .tg-input { padding-right: var(--s-10); }

/* Тач-таргет 44×44 без изменения вёрстки.

   Мелкие управляющие элементы — ссылки сортировки в шапке таблицы, глазок
   у пароля, переключатель приложений — по норме должны иметь площадь нажатия
   44×44, но растягивать их визуально нельзя: поплывёт плотность таблиц.
   Псевдоэлемент расширяет только область попадания, оставляя размер как был. */
.tg-tap { position: relative; }
.tg-tap::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 100%; height: 100%;
  min-width: var(--tap); min-height: var(--tap);
}

.tg-check { display: inline-flex; align-items: center; gap: var(--s-4); font-size: var(--fs-body); cursor: pointer; min-height: var(--ctl-md); }
.tg-check input[type='checkbox'], .tg-check input[type='radio'] {
  appearance: none; -webkit-appearance: none;
  width: 15px; height: 15px; margin: 0; flex: none;
  border: 1px solid var(--border-strong); border-radius: var(--r-1);
  background: var(--surface); cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.tg-check input[type='radio'] { border-radius: var(--r-full); }
.tg-check input:checked { background: var(--a-500); border-color: var(--a-500); }
.tg-check input[type='checkbox']:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-6'/%3E%3C/svg%3E");
  background-size: 13px; background-position: center; background-repeat: no-repeat;
}
.tg-check input[type='radio']:checked { box-shadow: inset 0 0 0 3px var(--surface); }
.tg-check input:focus-visible { outline: none; box-shadow: var(--ring); }
.tg-check input:indeterminate { background: var(--a-500); border-color: var(--a-500); background-image: linear-gradient(var(--surface), var(--surface)); background-size: 8px 2px; background-position: center; background-repeat: no-repeat; }

.tg-switch { position: relative; display: inline-flex; align-items: center; gap: var(--s-4); cursor: pointer; }
.tg-switch input { appearance: none; -webkit-appearance: none; width: 32px; height: 18px; margin: 0; border-radius: var(--r-full); background: var(--n-300); border: none; cursor: pointer; transition: background var(--dur-1) var(--ease); }
.tg-switch input::after { content: ''; position: absolute; top: 3px; left: 3px; width: 12px; height: 12px; border-radius: var(--r-full); background: #fff; transition: transform var(--dur-1) var(--ease); }
.tg-switch input:checked { background: var(--a-500); }
.tg-switch input:checked::after { transform: translateX(14px); }
.tg-switch input:focus-visible { outline: none; box-shadow: var(--ring); }

.tg-form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--s-5) var(--s-6); }
.tg-form-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s-5); }
.tg-fieldset { border: 0; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }
.tg-fieldset__legend { padding: 0 0 var(--s-4); border-bottom: 1px solid var(--hairline); width: 100%; }

/* ============ Панели и карточки ============ */
.tg-panel { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-3); }
.tg-panel__header { display: flex; align-items: center; justify-content: space-between; gap: var(--s-5); padding: var(--s-5) var(--s-6); border-bottom: 1px solid var(--hairline); min-height: 44px; }
/* minmax(0, 1fr) — та же причина, что у tg-list и tg-page, но эта колонка
   оказалась самой дорогой. Шаблоны сплошь пишут `tg-panel__body grid gap-5`:
   утилита даёт display: grid, колонок не задаёт, и получается одна auto-дорожка,
   а она тянется по max-content. Вложенный `grid-cols-3` просит три колонки по
   274px — дорожка становится 854px внутри панели шириной 319, и содержимое
   уходит за правый край экрана. Увидеть это нельзя: html и body на мобильном
   стоят с overflow-x: hidden, поэтому лишнее не прокручивается, а срезается.
   Правило действует, только когда шаблон сделал элемент гридом; свои колонки
   (grid-cols-2 и подобные) перебивают его, так как Tailwind грузится позже. */
.tg-panel__body { padding: var(--s-6); grid-template-columns: minmax(0, 1fr); }
.tg-panel__footer { padding: var(--s-5) var(--s-6); border-top: 1px solid var(--hairline); }
.tg-panel--flush .tg-panel__body { padding: 0; }
/* Модификатор задуман для списков и таблиц, которые сами держат поля в строке и
   должны тянуться до рамки панели. Но зануление доставалось любому телу панели,
   и обычный контент — полосы прогресса, заметки, формы, строки процесса —
   оказывался прижат к рамке вплотную, без единого отступа. Отступ возвращается
   тем телам, внутри которых широкого контента нет; списки и таблицы правило не
   трогает, а браузер без :has() остаётся при прежнем поведении. */
.tg-panel--flush .tg-panel__body:not(:has(> .tg-list, > .tg-table-wrap, > .tg-empty, > table)) { padding: var(--s-6); }

.tg-kpi { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-4); padding: var(--s-5) var(--s-6); background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-3); }
.tg-kpi__label { display: flex; align-items: center; gap: var(--s-3); }
.tg-kpi__value { font-family: var(--font-mono); font-size: var(--fs-metric); line-height: var(--lh-metric); font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-num); }
.tg-kpi__delta { font-family: var(--font-mono); font-size: var(--fs-meta); }
.tg-kpi--clickable { cursor: pointer; transition: border-color var(--dur-1) var(--ease); }
.tg-kpi--clickable:hover { border-color: var(--border-strong); }
/* Четыре и более KPI в ряд с денежными суммами: 28px не влезает в колонку, а
   обрезать сумму нельзя — усечённое число читается как другое число. Ступень
   вниз по шкале, шрифт и tabular-nums те же. */
.tg-kpi--dense .tg-kpi__value { font-size: var(--fs-title); line-height: var(--lh-title); }

/* ============ Плашки, чипы, статусы ============ */
.tg-badge {
  display: inline-flex; align-items: center; gap: var(--s-3);
  height: 20px; padding: 0 var(--s-4);
  font-family: var(--font-cond); font-size: var(--fs-micro); font-weight: 600;
  letter-spacing: 0.03em; text-transform: uppercase;
  border: 1px solid; border-radius: var(--r-1); white-space: nowrap;
}
.tg-badge__dot { width: 5px; height: 5px; border-radius: var(--r-full); flex: none; background: currentColor; }
.tg-badge--ok { color: var(--ok-fg); background: var(--ok-bg); border-color: var(--ok-bd); }
.tg-badge--warn { color: var(--warn-fg); background: var(--warn-bg); border-color: var(--warn-bd); }
.tg-badge--err { color: var(--err-fg); background: var(--err-bg); border-color: var(--err-bd); }
.tg-badge--info { color: var(--info-fg); background: var(--info-bg); border-color: var(--info-bd); }
.tg-badge--idle { color: var(--idle-fg); background: var(--idle-bg); border-color: var(--idle-bd); }
.tg-badge--count { font-family: var(--font-mono); text-transform: none; letter-spacing: 0; padding: 0 var(--s-3); }

.tg-chip {
  display: inline-flex; align-items: center; gap: var(--s-3);
  height: var(--ctl-sm); padding: 0 var(--s-4);
  font-size: var(--fs-meta); color: var(--fg-secondary);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-2);
  /* Высота чипа фиксирована, поэтому текст в нём не переносится: длинное
     название категории иначе распирает строку таблицы и вылезает за ячейку.
     transition по конкретным свойствам, а не all: анимируем цвет, не размеры. */
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
/* text-overflow не действует на флекс-контейнер, поэтому чип с голым текстом
   внутри срезался по краю без многоточия: «ВЛАДИМИР КРАВЧЕНКО» превращался в
   «ВЛАДИМИР КРАВЧ», и оборванное слово читалось как настоящее значение поля.
   Такому чипу хватает строчного блока — в нём многоточие работает. Чипы с
   вложенными элементами (иконка плюс подпись, счётчик реакции) остаются
   флексом, чтобы не трогать их выравнивание и зазор; усечение там уходит на
   сам вложенный элемент правилом ниже. */
.tg-chip:not(:has(> *)) { display: inline-block; line-height: calc(var(--ctl-sm) - 2px); }
.tg-chip > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tg-chip:hover { border-color: var(--border-strong); color: var(--fg); }
.tg-chip[aria-pressed='true'] { background: var(--a-tint-strong); border-color: var(--a-300); color: var(--a-500); }
.tg-chip__remove { color: var(--fg-faint); font-size: 13px; line-height: 1; }

.tg-sla { display: inline-flex; align-items: center; gap: var(--s-3); font-family: var(--font-mono); font-size: var(--fs-meta); }
.tg-sla__bar { width: 44px; height: 3px; border-radius: var(--r-full); background: var(--n-200); overflow: hidden; }
.tg-sla__fill { height: 100%; background: var(--ok-fg); }
.tg-sla--warn .tg-sla__fill { background: var(--warn-dot); }
.tg-sla--err .tg-sla__fill { background: var(--err-fg); }

/* ============ Таблица данных ============ */
.tg-table-wrap { overflow: auto; background: var(--surface); }
.tg-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--fs-body); }
.tg-table thead th {
  position: sticky; top: 0; z-index: var(--z-sticky);
  height: 32px; padding: 0 var(--s-5);
  font-family: var(--font-cond); font-size: var(--fs-micro); font-weight: 600;
  letter-spacing: var(--tracking-label); text-transform: uppercase; text-align: left;
  color: var(--fg-muted); background: var(--surface-sunken);
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
.tg-table thead th[aria-sort] { cursor: pointer; color: var(--fg-secondary); }
/* Заголовок-ссылка сортировки остаётся заголовком: цвет и регистр от th,
   а не фирменный синий ссылки — иначе шапка таблицы читается как набор ссылок. */
.tg-table thead th a { color: inherit; text-decoration: none; }
.tg-table thead th a:hover { color: var(--fg); }
/* Сортировка бывает и кнопкой (HTMX-таблицы портала): кнопка в браузере несёт
   собственные шрифт и цвет, поэтому без inherit шапка распадается на «часть
   заголовков — мелкий caps, часть — системный 13px». */
.tg-table thead th button { font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
.tg-table thead th button:hover { color: var(--fg); }
.tg-table thead th[aria-sort='ascending']::after { content: ' ↑'; color: var(--a-500); }
.tg-table thead th[aria-sort='descending']::after { content: ' ↓'; color: var(--a-500); }
.tg-table tbody td { height: var(--row-h); padding: 0 var(--s-5); border-bottom: 1px solid var(--hairline); vertical-align: middle; }
.tg-table tbody tr:hover td { background: var(--surface-hover); }
.tg-table tbody tr[aria-selected='true'] td { background: var(--surface-selected); }
/* nowrap обязателен: в узкой колонке «6 480 000» иначе разрывается по
   разрядам на три строки и читается как три разных числа. */
.tg-table .tg-td--num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; letter-spacing: var(--tracking-num); white-space: nowrap; }
/* nowrap по той же причине, что и у чисел: «INV-2026-000131» и barcode
   переносятся по дефисам на три строки и читаются как разные коды. */
.tg-table .tg-td--code { font-family: var(--font-mono); white-space: nowrap; }
.tg-table .tg-td--actions { text-align: right; white-space: nowrap; }
.tg-table tfoot td { height: 34px; padding: 0 var(--s-5); font-family: var(--font-mono); font-weight: 600; background: var(--surface-sunken); border-top: 1px solid var(--border); }
.tg-table--grid tbody td + td { border-left: 1px solid var(--hairline); }
.tg-table__row-actions { opacity: 0; transition: opacity var(--dur-1) var(--ease); }
.tg-table tbody tr:hover .tg-table__row-actions, .tg-table tbody tr:focus-within .tg-table__row-actions { opacity: 1; }

.tg-toolbar { display: flex; align-items: center; gap: var(--s-4); padding: var(--s-4) var(--s-5); border-bottom: 1px solid var(--hairline); flex-wrap: wrap; }
.tg-toolbar__spacer { flex: 1 1 auto; }
.tg-pagination { display: flex; align-items: center; justify-content: space-between; gap: var(--s-5); padding: var(--s-4) var(--s-5); border-top: 1px solid var(--hairline); font-size: var(--fs-meta); color: var(--fg-muted); }

/* ============ Списки ============ */
/* minmax(0, 1fr), а не 1fr: у grid-элемента min-width по умолчанию auto, и
   строка с длинным неразрывным содержимым (сербское название контрагента,
   номер счёта) растягивает весь список шире контейнера. Обрезка тогда живёт
   в родителе с overflow-x: hidden — текст просто пропадает за краем вместо
   многоточия. То же min-width: 0 нужно самой строке: она flex-контейнер. */
.tg-list { display: grid; grid-template-columns: minmax(0, 1fr); }
.tg-list__item { display: flex; align-items: center; gap: var(--s-5); min-width: 0; min-height: 44px; padding: var(--s-4) var(--s-5); border-bottom: 1px solid var(--hairline); }
.tg-list__item:hover { background: var(--surface-hover); }
.tg-list__item--selected { background: var(--surface-selected); box-shadow: inset 2px 0 0 var(--a-500); }
/* Карточка записи на узком экране: та же строка списка, но в два яруса —
   заголовок со статусом сверху, метаданные снизу. */
.tg-list__item--stacked { flex-direction: column; align-items: stretch; gap: var(--s-3); }
.tg-list__leading { flex: none; width: 28px; height: 28px; display: grid; place-items: center; border: 1px solid var(--hairline); border-radius: var(--r-2); background: var(--surface-sunken); color: var(--fg-muted); }
.tg-list__main { min-width: 0; flex: 1; }
.tg-list__title { font-size: var(--fs-body); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ============ Навигация ============ */
.tg-nav-item {
  display: flex; align-items: center; gap: var(--s-5);
  height: 30px; padding: 0 var(--s-4); margin: 1px 0;
  font-size: var(--fs-body); color: var(--fg-secondary);
  border-radius: var(--r-2); cursor: pointer;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.tg-nav-item:hover { background: var(--surface-hover); color: var(--fg); text-decoration: none; }
.tg-nav-item[aria-current='page'] { background: var(--a-tint-strong); color: var(--a-500); font-weight: 500; }
.tg-nav-item__badge { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-micro); color: var(--fg-faint); }
.tg-nav-group { margin-top: var(--s-6); }

.tg-tabs { display: flex; gap: var(--s-6); border-bottom: 1px solid var(--hairline); }
/* nowrap и flex: none обязательны: высота вкладки фиксирована, поэтому длинное
   название («Счета (Спецификации)») иначе переносится на вторую строку и вылезает
   за подчёркивание, а на узком экране вкладки сжимаются вместо прокрутки полосы. */
.tg-tab { flex: none; height: 34px; padding: 0 var(--s-1); font-size: var(--fs-body); line-height: 32px; color: var(--fg-muted); white-space: nowrap; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer; background: none; border-top: 0; border-left: 0; border-right: 0; }
.tg-tab:hover { color: var(--fg); }
.tg-tab[aria-selected='true'] { color: var(--fg); border-bottom-color: var(--a-500); font-weight: 500; }

.tg-breadcrumb { display: flex; align-items: center; gap: var(--s-4); font-size: var(--fs-meta); color: var(--fg-muted); }
.tg-breadcrumb__sep { color: var(--fg-faint); }

/* ============ Пайплайн импорта ============ */
.tg-step { display: grid; grid-template-columns: 18px 1fr; gap: var(--s-5); position: relative; padding-bottom: var(--s-6); }
.tg-step::before { content: ''; position: absolute; left: 8px; top: 20px; bottom: 0; width: 1px; background: var(--hairline); }
.tg-step:last-child::before { display: none; }
.tg-step__marker { width: 18px; height: 18px; border-radius: var(--r-full); border: 1px solid var(--border-strong); background: var(--surface); display: grid; place-items: center; font-family: var(--font-mono); font-size: 9px; color: var(--fg-muted); }
.tg-step--ok .tg-step__marker { border-color: var(--ok-bd); background: var(--ok-bg); color: var(--ok-fg); }
.tg-step--err .tg-step__marker { border-color: var(--err-bd); background: var(--err-bg); color: var(--err-fg); }
.tg-step--run .tg-step__marker { border-color: var(--a-300); background: var(--a-tint-strong); color: var(--a-500); }
.tg-step--warn .tg-step__marker { border-color: var(--warn-bd); background: var(--warn-bg); color: var(--warn-fg); }

/* Лента шагов. Утренняя цепочка — одиннадцать этапов подряд, и колонкой она
   занимает экран целиком, оттесняя всё остальное вниз. Слева направо она же
   читается как ход утра: во сколько что отработало. Прокрутка вбок, если
   шагов много, остаётся внутри ленты — страницу вбок не тянет. */
.tg-steps { display: flex; overflow-x: auto; }
.tg-steps .tg-step {
  flex: 1 0 auto; min-width: 76px;
  grid-template-columns: 1fr; justify-items: center; text-align: center;
  gap: var(--s-3); padding: 0 var(--s-3) var(--s-3);
}
/* Соединитель идёт от края маркера до края следующего: 12px — это его радиус
   (9px) плюс просвет. */
.tg-steps .tg-step::before {
  top: 9px; left: calc(50% + 12px); right: calc(-50% + 12px);
  bottom: auto; width: auto; height: 1px;
}

.tg-progress { height: 3px; background: var(--n-200); border-radius: var(--r-full); overflow: hidden; }
.tg-progress__fill { height: 100%; background: var(--a-500); transition: width var(--dur-3) var(--ease); }
.tg-progress--indeterminate .tg-progress__fill { width: 30%; animation: tg-slide 1.2s linear infinite; }
/* Заполнение как состояние: израсходованный лимит предоплаты, занятое место.
   Те же три семантики, что у бейджей, — один смысл красится одним цветом. */
.tg-progress--ok .tg-progress__fill { background: var(--ok-fg); }
.tg-progress--warn .tg-progress__fill { background: var(--warn-fg); }
.tg-progress--err .tg-progress__fill { background: var(--err-fg); }
/* Полоса с несколькими исходами одного прогона: отправлено и с ошибкой рядом.
   Сегменты лежат в общей полосе, поэтому цвет задаётся самому заполнению,
   а не контейнеру, как у --ok/--warn/--err выше. */
.tg-progress__fill--ok { background: var(--ok-fg); }
.tg-progress__fill--err { background: var(--err-fg); }
@keyframes tg-slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(400%); } }

/* ============ Оверлеи ============ */
.tg-scrim { position: fixed; inset: 0; background: var(--scrim); z-index: var(--z-modal); }
/* Подложка под модалкой: на широком экране центрирует, на узком прижимает к
   низу (см. блок узкого экрана в конце файла). Раньше центрирование жило
   утилитами в каждом шаблоне, поэтому шторка снизу получилась в трёх окнах из
   семи, а в остальных карточка висела посреди экрана и не помещалась в высоту.
   Правила ниже адресуют модалку через подложку — двух классов достаточно,
   чтобы перебить утилиты вроде max-w-md, которые остались в разметке. */
/* Слой обязателен и равен слою скрима: без него подложка остаётся z-index auto,
   а затемнение с числовым 50 рисуется поверх — модалка выглядела приглушённой,
   и elementFromPoint на поле формы возвращал скрим, то есть ввод не работал
   вовсе. Равного значения достаточно: подложка всегда стоит в разметке после
   затемнения, а при равном слое выигрывает поздний. */
.tg-modal-host { position: fixed; inset: 0; z-index: var(--z-modal); display: grid; place-items: center; padding: var(--s-6); overflow-y: auto; }
/* Свой слой нужен и самой карточке, а не только подложке: половина окон в
   системе собрана без tg-modal-host — скрим и tg-modal лежат соседями прямо в
   оверлее шаблона. Там карточка оставалась z-index auto и проигрывала скриму с
   числовым 50, хотя стоит в разметке позже: позиционированный сосед с числовым
   слоем всегда рисуется выше auto. Окно выглядело затемнённым и не принимало
   ни клик, ни прокрутку, ни закрытие — elementFromPoint в его центре возвращал
   скрим. Слой на единицу выше скрима перекрывает и этот случай, и вложенность
   в подложку, где сравнение идёт уже внутри её контекста. */
.tg-modal { position: relative; z-index: calc(var(--z-modal) + 1); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-4); box-shadow: var(--sh-modal); max-width: 560px; width: 100%; }
.tg-modal__header { display: flex; align-items: center; justify-content: space-between; padding: var(--s-6); border-bottom: 1px solid var(--hairline); }
.tg-modal__body { padding: var(--s-6); }
.tg-modal__footer { display: flex; justify-content: flex-end; gap: var(--s-4); padding: var(--s-5) var(--s-6); border-top: 1px solid var(--hairline); background: var(--surface-sunken); border-radius: 0 0 var(--r-4) var(--r-4); }
.tg-popover { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-3); box-shadow: var(--sh-pop); padding: var(--s-3); min-width: 180px; }
.tg-menu-item { display: flex; align-items: center; gap: var(--s-5); height: 28px; padding: 0 var(--s-4); border-radius: var(--r-2); font-size: var(--fs-body); color: var(--fg-secondary); cursor: pointer; }
.tg-menu-item:hover { background: var(--surface-hover); color: var(--fg); }
.tg-tooltip { background: var(--n-900); color: #fff; font-size: var(--fs-micro); padding: var(--s-2) var(--s-4); border-radius: var(--r-1); box-shadow: var(--sh-pop); }
.tg-toast { display: flex; gap: var(--s-5); align-items: flex-start; width: 320px; padding: var(--s-5); background: var(--surface); border: 1px solid var(--border); border-left: 2px solid var(--a-500); border-radius: var(--r-2); box-shadow: var(--sh-pop); }
.tg-toast--ok { border-left-color: var(--ok-fg); }
.tg-toast--err { border-left-color: var(--err-fg); }

/* Полоса массового действия: появляется, когда в списке что-то выбрано.
   Это оторванный слой поверх списка, поэтому тень (правило 6), но радиуса нет —
   полоса прижата к нижнему краю окна. Отступ под домашнюю полосу iOS считается
   здесь: в шаблоне env(safe-area-inset-bottom) писать нечем, кроме инлайна. */
.tg-actionbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-pop);
  display: flex; align-items: center; gap: var(--s-5); flex-wrap: wrap;
  padding: var(--s-5) var(--s-7);
  padding-bottom: max(var(--s-5), env(safe-area-inset-bottom));
  background: var(--surface); border-top: 1px solid var(--border);
  box-shadow: var(--sh-pop);
}

.tg-note { display: flex; gap: var(--s-5); padding: var(--s-5); border: 1px solid; border-radius: var(--r-2); font-size: var(--fs-body); }
/* Пятая семантика у плашки была не заведена, хотя «получилось» — такой же
   исход, как «ошибка» и «внимание»: подтверждение сохранения, успешный прогон. */
.tg-note--ok { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--fg); }
.tg-note--info { background: var(--info-bg); border-color: var(--info-bd); color: var(--fg); }
.tg-note--warn { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--fg); }
.tg-note--err { background: var(--err-bg); border-color: var(--err-bd); color: var(--fg); }

/* ============ Колесо выбора ============ */
/* Список-катушка на узком экране: пальцем по десяти периодам попадать точнее,
   чем в системный select. Выбранное значение показывает подсветка центральной
   полосы, цвет и вес — без подъёма масштабом и градиентных шторок по краям. */
/* Высота колеса и отбивки считаются от высоты строки, чтобы центр катушки
   совпадал с подсвеченной полосой без подгонки чисел в разметке. */
.tg-picker { position: relative; height: calc(var(--ctl-touch) * 4); overflow: hidden; }
.tg-picker__wheel {
  height: 100%; overflow-y: auto; overflow-x: hidden;
  scroll-snap-type: y mandatory; overscroll-behavior: contain; touch-action: pan-y;
  scroll-padding: calc(var(--ctl-touch) * 1.5) 0;
  -webkit-overflow-scrolling: touch;
}
.tg-picker__spacer { height: calc(var(--ctl-touch) * 1.5); }
.tg-picker__marker {
  position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
  height: var(--ctl-touch); background: var(--surface-selected);
  border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline);
  pointer-events: none;
}
.tg-picker__option {
  display: flex; align-items: center; justify-content: center;
  height: var(--ctl-touch); font-size: var(--fs-strong); color: var(--fg-muted);
  white-space: nowrap; user-select: none; cursor: pointer;
  scroll-snap-align: center;
  transition: color var(--dur-1) var(--ease);
}
.tg-picker__option[aria-selected='true'] { color: var(--fg); font-weight: 600; }

/* ============ Календарь платежей ============ */
/* Месяц как сетка дней: в каждом дне число и точки событий. Состояние дня —
   классом, а не набором утилит: выходной и праздник приглушены фоном,
   сегодня и выбранный день обведены, как «выбрано» в остальной системе. */
.tg-calendar { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--s-2); }
.tg-calendar__head { text-align: center; padding: var(--s-3) 0; }
.tg-calendar__day {
  position: relative; aspect-ratio: 1; min-width: 0;
  display: grid; place-items: center; align-content: center; gap: var(--s-2);
  padding: var(--s-3); border: 1px solid transparent; border-radius: var(--r-2);
  font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--fg-secondary);
  cursor: pointer; transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.tg-calendar__day:hover { background: var(--surface-hover); }
.tg-calendar__day--muted { background: var(--surface-sunken); color: var(--fg-muted); }
.tg-calendar__day--holiday { background: var(--warn-bg); color: var(--warn-fg); }
.tg-calendar__day--today { border-color: var(--a-500); font-weight: 600; color: var(--fg); }
.tg-calendar__day[aria-pressed='true'] { background: var(--surface-selected); border-color: var(--a-300); }
.tg-calendar__marks { display: flex; gap: var(--s-2); justify-content: center; }
.tg-calendar__mark { width: 5px; height: 5px; border-radius: var(--r-full); }
.tg-calendar__mark--in { background: var(--ok-dot); }
.tg-calendar__mark--out { background: var(--err-dot); }

/* Холст диаграммы до того, как им занялся Chart.js. Размер холсту задаёт
   библиотека и пишет его инлайном; пока этого не произошло — данных нет,
   скрипт не отработал, страница ещё грузится — у canvas остаются его
   собственные 300x150, и он торчит из контейнера шириной 160-285px, а на
   узком экране режется краем окна. Селектор намеренно смотрит на инлайновый
   стиль: как только библиотека проставила ширину, правило перестаёт
   действовать и не спорит с её расчётом пропорций. */
canvas:not([style*="width"]) { max-width: 100%; }

/* ============ Серии: доли одного целого ============ */
/* Точка легенды и заливка полосы берут цвет серии из одного места, поэтому
   сектор доната, строка легенды и полоса доли всегда одного цвета. Номер
   серии ставится в разметке через {% cycle %}, а не приходит из БД: цвет —
   способ различить строки в списке, а не свойство категории. */
.tg-series { --series: var(--series-8); }
.tg-series--1 { --series: var(--series-1); }
.tg-series--2 { --series: var(--series-2); }
.tg-series--3 { --series: var(--series-3); }
.tg-series--4 { --series: var(--series-4); }
.tg-series--5 { --series: var(--series-5); }
.tg-series--6 { --series: var(--series-6); }
.tg-series--7 { --series: var(--series-7); }
.tg-series--8 { --series: var(--series-8); }
.tg-dot { flex: none; width: 8px; height: 8px; border-radius: var(--r-full); background: var(--series, var(--series-8)); }
.tg-progress--series .tg-progress__fill { background: var(--series, var(--series-8)); }

/* ============ Зона перетаскивания файлов ============ */
/* Пунктир — единственное место в системе, где он уместен: он говорит «сюда
   можно бросить», а не рисует границу. Подсветка под курсором с файлом — тот
   же цвет, что у выбранной строки списка и колонки Kanban. Position задан
   здесь, потому что прозрачный input растягивается на всю зону. */
.tg-dropzone {
  position: relative;
  display: grid; justify-items: center; gap: var(--s-3);
  padding: var(--s-7) var(--s-6); text-align: center;
  border: 1px dashed var(--border-strong); border-radius: var(--r-3);
  color: var(--fg-muted);
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.tg-dropzone--over { background: var(--surface-selected); border-color: var(--a-300); }

/* ============ Пустые состояния и скелетоны ============ */
.tg-empty { display: grid; justify-items: center; gap: var(--s-5); padding: var(--s-11) var(--s-6); text-align: center; }
.tg-empty__icon { width: 36px; height: 36px; display: grid; place-items: center; border: 1px dashed var(--border-strong); border-radius: var(--r-3); color: var(--fg-faint); }
.tg-skeleton { background: linear-gradient(90deg, var(--surface-sunken) 25%, var(--n-150) 37%, var(--surface-sunken) 63%); background-size: 400% 100%; animation: tg-shimmer 1.4s ease infinite; border-radius: var(--r-1); height: 12px; display: block; }
@keyframes tg-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
/* Приглушение блока на время подгрузки: старые данные видно, но они помечены неактуальными */
.tg-busy { opacity: 0.5; transition: opacity var(--dur-1) var(--ease); }

/* ============ Kanban и чат ============ */
.tg-kanban { display: grid; grid-auto-flow: column; grid-auto-columns: 272px; gap: var(--s-5); overflow-x: auto; align-items: start; }
.tg-kanban__col { background: var(--surface-sunken); border: 1px solid var(--hairline); border-radius: var(--r-3); }
.tg-kanban__col-head { display: flex; align-items: center; justify-content: space-between; padding: var(--s-4) var(--s-5); border-bottom: 1px solid var(--hairline); }
.tg-kanban__card { display: grid; gap: var(--s-4); padding: var(--s-5); margin: var(--s-4); background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-2); cursor: grab; }
.tg-kanban__card:hover { border-color: var(--border-strong); }
/* Колонка под курсором с карточкой: подсветка обязана быть у самой колонки, а не
   у карточки — во время перетаскивания курсор над пустым местом, и подсказать,
   куда упадёт тикет, больше нечем. Цвет тот же, что у выбранной строки списка. */
.tg-kanban__col--over { background: var(--surface-selected); border-color: var(--a-300); }

/* Просмотр вложенного изображения. Снимок разглядывают на нейтральном фоне, а
   не на полотне интерфейса, поэтому у слоя своя тёмная подложка из статичной
   шкалы --n-* — она не переопределяется в .dark и одинакова в обеих темах.
   Кнопки в панели слоя перекрашиваются под неё, светлого варианта у них нет. */
.tg-lightbox { position: fixed; inset: 0; z-index: var(--z-toast); display: grid; place-items: center; background: rgba(1, 29, 56, 0.92); }
.tg-lightbox__bar {
  position: absolute; top: 0; left: 0; right: 0;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-5);
  padding: var(--s-5) var(--s-6); color: var(--n-200);
}
.tg-lightbox__bar .tg-btn { color: var(--n-200); }
.tg-lightbox__bar .tg-btn:hover { background: rgba(255, 255, 255, 0.12); color: var(--n-50); }
.tg-lightbox__image { max-width: 95vw; max-height: 85vh; object-fit: contain; border-radius: var(--r-3); }

/* Полоска живой осциллограммы при записи голосового. Высота приходит из данных
   микрофона и потому живёт инлайном, а цвет и скругление — здесь: красный тот
   же, что у плашки «идёт запись». */
.tg-recording-bar { border-radius: var(--r-full); background: var(--err-fg); }

/* Осциллограмма голосового сообщения: полоска сыграна или ещё нет. Высота
   каждой приходит из аудио и живёт инлайном, цвет — здесь. */
.tg-wave-bar { flex: 1; min-width: 2px; border-radius: var(--r-full); background: var(--n-300); transition: background var(--dur-1) var(--ease); }
.tg-wave-bar--played { background: var(--a-500); }
/* Картинка внутри сообщения: превью открывается в просмотрщике по клику. */
.tg-msg__image { max-width: 100%; max-height: 12rem; border-radius: var(--r-2); object-fit: cover; cursor: pointer; }

.tg-msg { display: grid; gap: var(--s-3); max-width: 62%; }
.tg-msg__bubble { padding: var(--s-4) var(--s-5); font-size: var(--fs-body); background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-3); }
.tg-msg--own { margin-left: auto; }
.tg-msg--own .tg-msg__bubble { background: var(--a-tint-strong); border-color: var(--a-200); }
.tg-msg__meta { font-family: var(--font-mono); font-size: var(--fs-micro); color: var(--fg-faint); }

/* ============ Утилиты каркаса ============ */
.tg-app { display: grid; grid-template-columns: var(--rail-w) 1fr; min-height: 100vh; }
.tg-rail { border-right: 1px solid var(--hairline); background: var(--surface); display: flex; flex-direction: column; }
.tg-topbar { display: flex; align-items: center; gap: var(--s-5); height: var(--topbar-h); padding: 0 var(--s-7); border-bottom: 1px solid var(--hairline); background: var(--surface); }
/* Марка. Единственное место, где работает --font-wordmark: гарнитура взята под
   латиницу «TOOLS-GO» и на кириллице выглядит чужой, поэтому в заголовки и
   подписи она не идёт. Токен был заведён при установке системы, но в разметке
   не использовался — название набиралось общим шрифтом. */
.tg-wordmark {
  font-family: var(--font-wordmark);
  font-size: var(--fs-card); line-height: var(--lh-card); font-weight: 700;
  letter-spacing: var(--tracking-title); color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* minmax(0, 1fr) по той же причине, что и у tg-list: колонка grid по умолчанию
   не уже своего содержимого, поэтому одна длинная строка внутри любой панели
   растягивала всю страницу за пределы окна. Наружный контейнер прячет
   переполнение, так что выглядело это как молча обрезанный по правому краю
   текст, а не как полоса прокрутки. */
.tg-page { padding: var(--s-7); display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-6); align-content: start; }
.tg-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ============ Геройский блок ============ */
/* Брендовая половина экрана входа. Плоская заливка вместо градиентов, блобов
   и glass (правило 10): фон и текст берутся из статичной шкалы нейтралей —
   --n-* не переопределяются в .dark, поэтому блок остаётся тёмным в обеих
   темах и не спорит с --canvas. */
.tg-hero {
  display: grid; align-content: center; justify-items: center; gap: var(--s-7);
  padding: var(--s-11) var(--s-7);
  background: var(--n-900); color: var(--n-50); text-align: center;
}
.tg-hero__mark { width: 72px; height: 72px; color: var(--n-50); }
.tg-hero__title { font-size: var(--fs-hero); line-height: var(--lh-hero); font-weight: 600; letter-spacing: var(--tracking-title); margin: 0; }
.tg-hero__lead { font-size: var(--fs-strong); line-height: var(--lh-strong); color: var(--n-300); margin: 0; max-width: 46ch; }
.tg-hero__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--s-5); width: 100%; max-width: 560px; }
.tg-hero__card { padding: var(--s-6); border: 1px solid var(--n-700); border-radius: var(--r-3); text-align: left; }
.tg-hero__card-title { font-size: var(--fs-body); font-weight: 600; margin: 0 0 var(--s-3); }
.tg-hero__card-text { font-size: var(--fs-meta); line-height: var(--lh-meta); color: var(--n-400); margin: 0; }

/* ============ Каркас узкого экрана ============ */
/* Нижняя навигация и мобильная шапка — постоянный каркас, поэтому слой у них
   --z-rail, а не модальный: на равном слое выигрывал бы тот, кто ниже в
   разметке, и полоса навигации перекрывала бы каждое диалоговое окно.
   Отбивки под чёлку и домашнюю полосу считаются здесь: в шаблоне
   env(safe-area-inset-*) писать нечем, кроме инлайна. */
.tg-topbar--mobile {
  position: sticky; top: 0; z-index: var(--z-sticky);
  height: auto; min-height: var(--topbar-h);
  padding-top: max(var(--s-4), env(safe-area-inset-top));
  padding-bottom: var(--s-4);
  justify-content: space-between;
}

/* Полоса и ряд пунктов разведены: ветка навигации (апекс, финансы, поддержка,
   BPM) выбирается в шаблоне условием и приходит отдельным элементом со своим
   переходом. Будь раскладка на самой полосе, этот элемент стал бы единственным
   flex-ребёнком и забрал всю ширину, а пункты внутри легли бы как попало. */
.tg-bottomnav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-rail);
  background: var(--surface); border-top: 1px solid var(--hairline);
  padding-bottom: env(safe-area-inset-bottom);
}
.tg-bottomnav__row { display: flex; align-items: stretch; justify-content: space-around; }
/* Пункт целиком — цель для пальца, поэтому высота от --ctl-touch, а не от
   размера иконки. Подпись микро-размером системы: 8px, стоявшие тут раньше,
   не читаются ни на одном экране. */
.tg-bottomnav__item {
  flex: 1 1 0; min-width: 0;
  display: grid; justify-items: center; align-content: center; gap: var(--s-2);
  min-height: var(--ctl-touch); padding: var(--s-3) var(--s-2);
  color: var(--fg-muted); text-decoration: none;
  transition: color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.tg-bottomnav__item:hover { color: var(--fg); text-decoration: none; }
.tg-bottomnav__item[aria-current='page'] { color: var(--a-500); background: var(--a-tint); }
/* Низ рельса на узком экране. Панель выдвигается поверх нижней навигации и
   своим краем уходит под неё, поэтому подвал добирает высоту полосы плюс
   домашнюю полосу iOS — иначе кнопка выхода оказывается под навигацией.
   Отступ живёт здесь, а не инлайном в шаблоне: инлайн перебил бы и десктоп,
   где полосы навигации нет вовсе. */
/* Имя класса удвоено намеренно. На обоих элементах в разметке висит утилита
   отступа (p-3 и p-4), у неё тот же вес селектора, а грузится Tailwind позже —
   при одинарном классе выигрывала бы она, и правило просто не срабатывало.
   Медиа-запрос веса не добавляет. */
@media (max-width: 767.98px) {
  .tg-rail__footer.tg-rail__footer { padding-bottom: calc(var(--ctl-touch) + var(--s-7) + env(safe-area-inset-bottom)); }
  /* Столько же — под содержимым страницы: последняя строка списка иначе
     оказывается под полосой навигации и до неё не дотянуться. */
  .tg-content.tg-content { padding-bottom: calc(var(--ctl-touch) + var(--s-7) + env(safe-area-inset-bottom)); }

  /* Страховка от срезания. pwa_base.css ставит на html и body
     overflow-x: hidden, и всё, что шире экрана, не прокручивается, а молча
     пропадает за правым краем: обрезанная колонка, половина суммы, кнопка,
     до которой не добраться. Проверить это замером тоже нельзя — при
     overflow-x: hidden scrollWidth не превышает clientWidth, и любая проверка
     на переполнение отвечает «всё в порядке».

     Прокрутка внутри области содержимого — не решение задачи, а нижняя
     граница приличия: широкий блок остаётся достижимым, пока его не научили
     переноситься. Шапка и полоса навигации при этом стоят на месте, потому
     что лежат вне .tg-content. */
  .tg-content.tg-content { overflow-x: auto; }
}

.tg-bottomnav__label {
  font-family: var(--font-display);
  font-size: var(--fs-micro); line-height: var(--lh-micro); font-weight: 600;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Счётчик непрочитанного на пункте. Сидит на иконке, поэтому позиционируется
   от неё, а не от пункта: у пункта под иконкой ещё подпись, и от его угла
   счётчик уезжал бы в пустоту. Цвет — семантика ошибки: это то, что требует
   внимания, а не просто число. */
.tg-bottomnav__item { position: relative; }
.tg-bottomnav__badge {
  position: absolute; top: var(--s-2); left: 50%; margin-left: var(--s-3);
  min-width: 16px; height: 16px; padding: 0 var(--s-2);
  display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 9px; font-weight: 600; line-height: 1;
  color: var(--fg-on-accent); background: var(--err-fg); border-radius: var(--r-full);
}

/* ============ Палец вместо курсора ============ */
/* Запрос отдельный от ширины окна: тач бывает и на большом мониторе, и мышь на
   узком.

   Здесь только ЗОНА нажатия, но не габарит. Первая версия растила сам размер —
   чипы и мелкие кнопки с 26 до 36px, вкладки до 44 — и на плотных экранах это
   сложилось в общее ощущение раздутого интерфейса: на экран стало влезать
   заметно меньше при том же содержимом. Видимый размер вернулся к прежнему, а
   44px добирает прозрачный псевдоэлемент: палец попадает, плотность цела.

   Расширять зону можно только туда, где нет соседа. У чипов, вкладок и
   кнопок-иконок соседи стоят слева и справа, поэтому растём вверх-вниз. У
   пунктов рельса и меню соседи сверху и снизу — там растёт сама высота, иначе
   зоны наложатся друг на друга и палец попадёт не в тот пункт. Обе они —
   строки навигации, а не плотное содержимое, и 44px для них норма. */
@media (pointer: coarse) {
  .tg-btn--icon, .tg-btn--sm, .tg-chip, .tg-tab { position: relative; }
  .tg-btn--icon::after, .tg-btn--sm::after, .tg-chip::after, .tg-tab::after {
    content: ''; position: absolute; left: 0; right: 0;
    top: 50%; transform: translateY(-50%); height: var(--ctl-touch);
  }
  .tg-menu-item { height: var(--ctl-touch); }
  .tg-nav-item { height: var(--ctl-touch); }
}

/* ============ Узкий экран ============ */
/* 767.98px, а не 768: утилиты Tailwind md:* включаются ровно с 768px, и при
   границе 768 оба набора действовали одновременно — мобильная карточка и
   настольная таблица показывались вместе. */
@media (max-width: 767.98px) {
  .tg-app { grid-template-columns: 1fr; }
  /* Рельс здесь раньше просто гасился display: none — правило писалось, когда
     класс в разметке не использовался вообще и «на телефоне рельса нет»
     звучало разумно. На деле рельс на узком экране никуда не девается, он
     становится выдвижной панелью: уезжает за левый край и возвращается по
     кнопке. Гасить его нельзя — бургер открывал бы пустоту. Само уезжание
     задаёт разметка сдвигом, потому что состояние живёт в Alpine. */
  .tg-page { padding: var(--s-5) var(--s-5) 72px; }

  /* Модалка становится шторкой снизу: до верхнего края экрана палец не
     дотягивается, а карточка по центру на 375px всё равно занимает почти весь
     экран — но с полями, которые некуда деть. Прокручивается только тело:
     шапка с заголовком и футер с кнопками остаются на виду. */
  .tg-modal-host { padding: 0; align-items: end; }
  .tg-modal-host .tg-modal {
    width: 100%; max-width: none; max-height: 92dvh;
    display: flex; flex-direction: column;
    border-radius: var(--r-4) var(--r-4) 0 0; border-bottom: 0;
  }
  .tg-modal-host .tg-modal__body { overflow-y: auto; overscroll-behavior: contain; }
  .tg-modal-host .tg-modal__footer {
    border-radius: 0;
    padding-bottom: max(var(--s-5), env(safe-area-inset-bottom));
  }

  /* Лента шагов разворачивается обратно в колонку. Одиннадцать маркеров в
     строку на 375px не помещаются никак, а прокрутка вбок прячет хвост цепочки
     за краем — именно тот, где утро обычно и ломается. Колонка длинная, зато
     видно всю. */
  .tg-steps { display: block; overflow-x: visible; }
  .tg-steps .tg-step {
    grid-template-columns: 18px 1fr; justify-items: start; text-align: left;
    gap: var(--s-5); padding: 0 0 var(--s-6);
  }
  .tg-steps .tg-step::before {
    top: 20px; left: 8px; right: auto; bottom: 0; width: 1px; height: auto;
  }

  /* Шапка панели переносит содержимое. Заголовок и крупное число стоят в одном
     ряду, число с flex-shrink-0, а у заголовка нет ни обрезки, ни переноса:
     колонка с ним сжималась до нуля, текст вылезал за свои границы и
     печатался ПОВЕРХ числа — «Ожидаемые поступления» читалось вперемешку с
     «+59 851 743». Перенос убирает наложение, не пряча ни то, ни другое. */
  .tg-panel__header { flex-wrap: wrap; }

  /* Крупная цифра на ступень мельче. 28px моноширинного на десятизначном
     числе — это больше 200px, то есть две трети узкого экрана под одно
     значение. */
  .tg-metric { font-size: var(--fs-title); line-height: var(--lh-title); }

  /* Строка списка переносит содержимое по той же причине: подпись слева и
     сумма справа вместе шире экрана, и сумма уезжала за правый край. */
  .tg-list__item { flex-wrap: wrap; }

  /* Группа кнопок — это переключатель одного значения (период, режим), её
     нельзя ни перенести, ни сжать: кнопки склеены общими границами, и разрыв
     строки превратил бы один переключатель в два. Поэтому прокрутка вдоль.
     На 375px группа периодов занимает 419px и без этого просто срезалась. */
  .tg-btn-group { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
  .tg-btn-group::-webkit-scrollbar { display: none; }

  /* Распорка прижимает действия к правому краю, когда полоса в одну строку.
     На 375px строка всё равно переносится, и распорка занимает собственный ярус
     между полем и кнопками — пустой, но настоящий. Прокрутку вдоль вместо
     переноса здесь пробовали: полоса ужимается со 129px до 77, но главная
     кнопка уезжает за правый край и её просто не видно. Перенос честнее. */
  .tg-toolbar__spacer { display: none; }

  /* Счётчик и кнопки страниц в строку не помещаются и налезают друг на друга. */
  .tg-pagination { flex-direction: column; align-items: stretch; gap: var(--s-4); }

  .tg-table-wrap { -webkit-overflow-scrolling: touch; }

  /* Колонка шире экрана, поэтому её край всегда обрезан и непонятно, доска
     кончилась или нет. Прилипание по колонке даёт границу, к которой доска
     возвращается сама. */
  .tg-kanban { scroll-snap-type: x mandatory; }
  .tg-kanban__col { scroll-snap-align: start; }

  /* 62% от 375px — пузырь в 232px: в него не влезает ни одна фраза целиком. */
  .tg-msg { max-width: 85%; }

  /* День календаря — цель для пальца, а не только надпись. */
  .tg-calendar__day { min-height: var(--ctl-touch); }
}
