/* LinesERP — оформление. Набор тем, мобильная раскладка.

   Одна шкала на всё: отступы кратны 4, радиусы и тени заданы токенами,
   цвет текста и фона нигде не пишется числом. Поэтому вид приложения
   меняется правкой этого блока, а не поиском по разметке. */

:root {

  /* Шкала кегля. База 16px, каждый шаг — целое число пикселей: дробный кегль
     мылит текст и уводит строки с общей линии. Восемь ступеней, не двадцать
     четыре: если разница между соседними меньше пикселя, иерархии нет. */
  --t-2xs: .75rem;     /* 12px — счётчики, дни в шапке диаграммы */
  --t-xs:  .8125rem;   /* 13px — мета, вторичные подписи */
  --t-sm:  .875rem;    /* 14px — плотные таблицы, подписи полей */
  --t-base:.9375rem;   /* 15px — основной текст интерфейса */
  --t-md:  1.0625rem;  /* 17px — заголовки карточек, крупные значения */
  --t-lg:  1.1875rem;  /* 19px — заголовки разделов */
  --t-xl:  1.4375rem;  /* 23px — заголовок внутреннего экрана */
  --t-2xl: 1.75rem;    /* 28px — заголовок главной */

  /* Моноширинный при том же кегле читается мельче: у IBM Plex Mono уже очко
     и шире межбуквенное. Размерным надписям — дате, проценту, длительности —
     даём ступень сверху, иначе в карточках они выглядят набранными для мыши,
     а не для глаза. */
  --t-mono: .8125rem;  /* 13px */

  /* Шкала отступов, шаг 4px. Всё расстояние в интерфейсе берётся отсюда,
     иначе колонки соседних блоков не встают на общую линию. */
  --s-05: 2px;  --s-1: 4px;  --s-2: 8px;   --s-3: 12px;
  --s-4:  16px; --s-5: 24px; --s-6: 32px;  --s-7: 48px;

  /* Одна шкала формы на всё приложение. Правило: метки и мелкие плашки — sm,
     кнопки, поля и карточки — базовый, панели и окна — lg, пилюли — полный. */
  /* Ширина боковой колонки: по ней же выравнивается начало разделов в шапке.
     248px держит «Реконструкция особняка на Пречистенке» в две строки без
     многоточия на первом же слове */
  --side-w: 248px;
  /* Ширина колонки текста в базе знаний: строка длиннее читается тяжело,
     а инструкцию читают глазами, а не пролистывают */
  --kb-line: 760px;
  /* Кружок хода задачи. Один размер на главную, «Мои задачи» и канбан:
     это одна и та же кнопка, и разъезжаться по экранам ей нельзя.
     26px против прежних 22: в 22 кружок читался как значок при названии,
     а он — мишень, по которой задачу закрывают, не открывая. */
  --step-size: 26px;
  --radius-sm:   6px;
  --radius:      8px;
  --radius-lg:   12px;
  --radius-xl:   18px;   /* полотно страницы */
  --radius-full: 999px;

  --font: 'Onest', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Одна кривая на все переходы: интерфейс должен ощущаться цельным */
  --ease: cubic-bezier(.4, 0, .2, 1);
  --fast: .14s var(--ease);
}

/* ── Темы ─────────────────────────────────────────────────────────────────
   Тема — это только цвет: шкалы кегля, отступов и радиусов у всех общие.
   Каждая тема задаёт один и тот же перечень токенов целиком, без наследования
   от соседней: иначе правка одной темы тихо меняет другую.

   Селектор — атрибут, а не `:root[…]`, чтобы тему можно было включить и на
   отдельном узле: на этом держатся образцы тем в настройках.

   `data-scheme` (light/dark) ставится рядом с `data-theme` и отвечает за то,
   что зависит не от конкретной темы, а от стороны света: `color-scheme`,
   цвет заметок-стикеров, подсказка диаграммы.

   Шесть светлых и шесть тёмных: человек выбирает себе одну с каждой стороны,
   а чем и когда они сменяются — режимом в «Оформлении».

   Восемь из двенадцати собраны 26 августа 2026 по присланным палитрам и
   названы их именами — Onyx, Periwinkle, Burgundy. Название на языке
   палитры честнее перевода: «Барвинок» не говорит ничего, а Periwinkle —
   это ровно тот цвет, что в ней лежит.

   Про яркость. Первая попытка вышла блёклой, потому что акценты гасились
   ради контраста с белой подписью. Это неверный ход: контраст решается
   не приглушением цвета, а цветом подписи на нём. Золото и циан остались
   золотом и цианом — подпись на них тёмная. Гасить пришлось бы, только
   если бы подпись обязана была быть белой, а она не обязана.

   Два токена сверх прежних:
     --accent-grad  заливка главной кнопки; у спокойных тем почти плоская,
                    у ярких — переход внутри одного цвета, а не радуга;
     --bg-grad      мягкое свечение под содержимым, из верхнего угла. Оно
                    едва заметно и обязано таким остаться: под текстом
                    фон не должен быть событием.

   Ещё два, 26 августа 2026, — про то, что лежит на чём:

     --canvas       заливка полотна-острова (.main);
     --raised       заливка того, что на полотне лежит: карточка задачи,
                    дедлайн, окно, список;
     --well         заливка того, что в полотно утоплено: колонка канбана.
                    Одна мягкая ступень вниз — ровно столько, чтобы колонка
                    читалась границей, а не плитой.

   До них и полотно, и карточка брали один и тот же --surface, то есть
   совпадали до байта. На тёмных темах это сходило с рук — карточку
   очерчивала рамка, которая там светлее поля, — а на светлых карточка
   пропадала: на «Моих задачах» было видно только контур, и задача,
   у которой не горел красный просроченный фон, сливалась с листом.

   Лечится не «сделать карточку светлее»: у светлых тем --surface уже
   упирается в белое, выше некуда. Двигаться должно полотно. Получается
   лестница в три ступени вместо двух: фон → полотно → карточка.

   Насколько светлее — вопрос отдельный, и первый ответ был неверным.
   Сперва --raised взяли как белую точку самой темы: тот же оттенок,
   поднятый до предела. У «Gray Slate» это дало #fffefc, то есть почти
   чистый белый, и карточки на приглушённом алебастровом полотне резали
   глаз — тема, выбранная за спокойный тон, получала самые яркие пятна
   на экране. У «Дня», где полотно и так почти белое, тот же приём вышел
   удачным (1,07) — по нему и выровняли остальных.

   Теперь --raised у всех тем стоит на 1,07 от полотна: не «как можно
   белее», а «на одну различимую ступень выше». У светлых белизна вышла
   96–99% вместо 100% — оттенок темы на карточке сохраняется, кремовый
   остаётся кремовым, а не выцветает в бумагу.

   У тёмных тем ступень появилась 27 августа. Сперва её не сделали: полотну
   вниз некуда — «Синяя ночь» и так отличается от фона всего в 1,12, — а
   наверх карточку не пускал --surface-2, занятый колонками канбана.
   Решение оказалось не в том, чтобы двигать карточку, а в том, чтобы дать
   колонке свой токен: как только колонка съехала на --well, карточка
   спокойно поднялась над полотном на те же 1,07, что и у светлых.

   Пока ступени не было, --canvas и --raised у тёмных были равны --surface,
   то есть карточка на «Моих задачах» совпадала с фоном ровно — 1,00,
   и её не было видно вовсе. */

/* ── Светлая «День» — холодная белая с лазурью, вид по умолчанию ────────── */
:root,
[data-theme='day'] {
  --bg: #e9edf3;
  --surface: #fafcfe;
  --surface-2: #dfe4ec;
  --surface-3: #d2d8e3;
  /* Полотно на ступень ниже карточки, карточка — белая точка темы */
  --canvas: #f1f5f9;
  --raised: #fafdff;
  --well: #e9edf3;
  --border: #d9dee7;
  --border-strong: #c3c9d4;
  --text: #171b22;
  --text-dim: #58606d;
  --text-faint: #89909c;

  --accent: #3c6fa8;
  --accent-hover: #2f5a8b;
  --accent-soft: #e8eff7;
  --accent-text: #ffffff;
  --accent-ring: rgba(60, 111, 168, .18);

  --ok: #2e7d57;
  --ok-soft: #e6f2eb;
  --warn: #8c6516;
  --warn-soft: #f6f0e2;
  --critical: #b44a44;
  --critical-soft: #f8ebea;

  --bar: #4d7fb5;
  --bar-done: #35618f;
  --summary: #3b434f;
  --milestone: #8c6516;
  --off: #dfe4ec;
  --off-holiday: #f6ecec;
  --today: #b44a44;

  /* Тени подкрашены в тон фона: чёрных теней на светлом не бывает */
  --shadow-xs: 0 1px 2px rgba(23, 27, 34, .04);
  --shadow: 0 1px 3px rgba(23, 27, 34, .06);
  --shadow-md: 0 6px 18px rgba(23, 27, 34, .09);
  --shadow-lg: 0 18px 44px rgba(23, 27, 34, .16);
  --accent-grad: linear-gradient(135deg, #4a82bd 0%, #3c6fa8 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(60, 111, 168, .14) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(60, 111, 168, .06) 0%, transparent 60%);
}

/* ── Светлая «Kraft» — конвертный крем и печатный синий ───────────────────
   Прежде звалась «Бумага». Имя сменили 27 августа: остальные светлые темы
   зовутся по своим палитрам, и одно русское слово среди них выпадало, а
   «бумага» вдобавок ничего не говорит про цвет — бумага бывает любой.
   Kraft — тот самый оттенок канцелярского конверта, и он тут в основе.

   Лист тёплый, но не жёлтый. Первая редакция была кремовой до желтизны —
   и вышла не спокойной, а выцветшей: жёлтое на большой площади читается
   не как бумага, а как старая бумага. Теперь теплота едва заметна, ближе
   к нейтральному серому, чем к охре: экран не белый, но и не желтит.

   Всё живое — в одном синем, том, каким печатают ссылки. Второго яркого
   цвета нет. */
[data-theme='paper'] {
  --bg: #e8e5df;
  --surface: #faf8f6;
  --surface-2: #e1ded6;
  --surface-3: #d9d5cc;
  --canvas: #f6f4f1;
  --raised: #fdfcfa;
  --well: #eeebe7;
  --border: #e3e0d9;
  --border-strong: #cfc9bf;
  --text: #1f1d1a;
  --text-dim: #57534e;
  --text-faint: #87817a;

  --accent: #1568b8;
  --accent-hover: #10559a;
  --accent-soft: #e2edf8;
  --accent-text: #ffffff;
  --accent-ring: rgba(21, 104, 184, .18);

  --ok: #2f7048;
  --ok-soft: #e4eee4;
  --warn: #8a5d0f;
  --warn-soft: #f5ecd9;
  --critical: #b4453a;
  --critical-soft: #f6e7e2;

  --bar: #3d84cf;
  --bar-done: #1568b8;
  --summary: #47413a;
  --milestone: #8a5d0f;
  --off: #e1ded6;
  --off-holiday: #f3e6e0;
  --today: #b4453a;

  --shadow-xs: 0 1px 2px rgba(60, 48, 30, .05);
  --shadow: 0 1px 3px rgba(60, 48, 30, .07);
  --shadow-md: 0 6px 18px rgba(60, 48, 30, .10);
  --shadow-lg: 0 18px 44px rgba(60, 48, 30, .17);
  --accent-grad: linear-gradient(135deg, #2478cc 0%, #1568b8 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(21, 104, 184, .10) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(138, 93, 15, .07) 0%, transparent 60%);
}

/* ── Светлая «Sage» — бумага из вторсырья, приглушённый лесной ────────────
   Соседка Kraft, а не отдельная затея: фон той же светлоты (0,788 против
   0,785), только уведён в зелень. Рядом они читаются как два сорта одной
   бумаги — конвертная и та, что из вторсырья.

   Зелень намеренно тихая. «Эко» в интерфейсах обычно означает ядовитый
   салатовый, который к третьему часу работы начинает мешать; здесь она
   заметна, только если положить рядом серый лист. Акцент — приглушённый
   лесной, холоднее травяного и мягче хвойного: так он не спорит с зелёным
   «готово» в статусах, которое рядом обязано читаться отдельно. */
[data-theme='sage'] {
  --bg: #e4e7db;
  --surface: #f8f8f3;
  --surface-2: #dde0d2;
  --surface-3: #d4d7c7;
  --canvas: #f4f5ee;
  --raised: #fcfcf8;
  --well: #ebede3;
  --border: #dee2d4;
  --border-strong: #c8cdba;
  --text: #1c1f18;
  --text-dim: #54584c;
  --text-faint: #888c7f;

  --accent: #4d7c4a;
  --accent-hover: #3e653c;
  --accent-soft: #e2e8dc;
  --accent-text: #ffffff;
  --accent-ring: rgba(77, 124, 74, .18);

  --ok: #2f7048;
  --ok-soft: #e2ece5;
  --warn: #8a6011;
  --warn-soft: #f1ecdd;
  --critical: #b4453a;
  --critical-soft: #f3e7e3;

  --bar: #6d9c6a;
  --bar-done: #4d7c4a;
  --summary: #3f433a;
  --milestone: #8a6011;
  --off: #dde0d2;
  --off-holiday: #eee4e0;
  --today: #b4453a;

  --shadow-xs: 0 1px 2px rgba(30, 38, 22, .05);
  --shadow: 0 1px 3px rgba(30, 38, 22, .07);
  --shadow-md: 0 6px 18px rgba(30, 38, 22, .10);
  --shadow-lg: 0 18px 44px rgba(30, 38, 22, .16);
  --accent-grad: linear-gradient(135deg, #5a8b57 0%, #4d7c4a 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(77, 124, 74, .10) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(77, 124, 74, .05) 0%, transparent 60%);
}

/* ── Светлая «Linen» — лён, тёмное вино, шалфей ───────────────────────────
   Linen #F0E5DE · Dark Wine #6F1D1B · Ash Grey #ADBDAB
   Самая тихая из светлых: ни одного чистого цвета. Вино в акценте тёмное,
   и подпись на нём белая — светлое вино пришлось бы подпирать тёмной
   подписью, а кнопка с тёмной подписью читается как отключённая. */
[data-theme='linen'] {
  --bg: #e7d9d0;
  --surface: #f7efe9;
  --surface-2: #e7d9d0;
  --surface-3: #d1c0b5;
  /* Полотно на ступень ниже карточки, карточка — белая точка темы */
  --canvas: #f3e8e0;
  --raised: #f9f0e8;
  --well: #ecdfd7;
  --border: #dacbc1;
  --border-strong: #c3b2a8;
  --text: #2a201c;
  --text-dim: #6a5b54;
  --text-faint: #99887f;

  --accent: #8e2320;
  --accent-hover: #6f1d1b;
  --accent-soft: #f6e3e1;
  --accent-text: #ffffff;
  --accent-ring: rgba(142, 35, 32, .20);

  --ok: #557a52;
  --ok-soft: #e8efe6;
  --warn: #96690f;
  --warn-soft: #f7eeda;
  --critical: #b03a30;
  --critical-soft: #f9e8e5;

  --bar: #adbdab;
  --bar-done: #8e2320;
  --summary: #4a3c37;
  --milestone: #96690f;
  --off: #dccec4;
  --off-holiday: #f2ddd9;
  --today: #b03a30;

  --shadow-xs: 0 1px 2px rgba(70, 45, 35, .05);
  --shadow: 0 1px 3px rgba(70, 45, 35, .07);
  --shadow-md: 0 6px 18px rgba(70, 45, 35, .11);
  --shadow-lg: 0 18px 44px rgba(70, 45, 35, .18);
  --accent-grad: linear-gradient(135deg, #a52b26 0%, #6f1d1b 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(142, 35, 32, .16) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(173, 189, 171, .26) 0%, transparent 60%);
}

/* ── Светлая «Ultra Gray» — монохромная серая, без единого оттенка ────────
   Заводилась 27 августа взамен Periwinkle. Здесь нет цвета вовсе: фон,
   полотно, карточки, рамки и подписи — чистый нейтральный серый, у которого
   красная, зелёная и синяя составляющие равны. Даже акцент серый, почти
   чёрный: главная кнопка на нём читается как главная не краской, а тоном.

   Смысл в том, чтобы цветным на экране осталось только то, что цветом
   что-то значит: просрочка, готовность, метки типов и стикеры. В темах
   с оттенком они спорят с фоном; здесь спорить не с чем.

   Единственные цветные токены — состояния (--ok, --warn, --critical) и
   полосы диаграммы. Их обесцвечивать нельзя: «готово» и «горит» различают
   по цвету, а не по светлоте. */
[data-theme='ultragray'] {
  --bg: #e6e6e6;
  --surface: #f9f9f9;
  --surface-2: #dfdfdf;
  --surface-3: #d6d6d6;
  --canvas: #f5f5f5;
  --raised: #fdfdfd;
  --well: #ececec;
  --border: #e1e1e1;
  --border-strong: #c2c2c2;
  --text: #171717;
  --text-dim: #525252;
  --text-faint: #8a8a8a;

  --accent: #3f3f46;
  --accent-hover: #27272a;
  --accent-soft: #e8e8e9;
  --accent-text: #ffffff;
  --accent-ring: rgba(63, 63, 70, .18);

  --ok: #2f7048;
  --ok-soft: #e6eee9;
  --warn: #8a6011;
  --warn-soft: #f2ece0;
  --critical: #b4453a;
  --critical-soft: #f4e7e5;

  --bar: #6b6b75;
  --bar-done: #3f3f46;
  --summary: #3a3a3f;
  --milestone: #8a6011;
  --off: #dfdfdf;
  --off-holiday: #efe6e4;
  --today: #b4453a;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .05);
  --shadow: 0 1px 3px rgba(0, 0, 0, .07);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .10);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .16);
  --accent-grad: linear-gradient(135deg, #52525b 0%, #3f3f46 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(0, 0, 0, .05) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(0, 0, 0, .03) 0%, transparent 60%);
}

/* ── Светлая «Ultraviolet» — лиловый лист и насыщенный фиолет ─────────────
   Заводилась 27 августа взамен «Old Gold», которая оказалась ненужной:
   кремовая до желтизны, с почти чёрной вишней в акценте, она не пришлась
   никому.

   Здесь строй другой: лист холодный, чуть лиловый, а всё живое собрано
   в одном насыщенном фиолете — кнопки, ссылки, выбранный раздел. Ни одного
   второго яркого цвета: остальное решается тоном, а не краской. Такой
   расклад держит внимание на содержимом и при этом не выглядит серым.

   Диапазон шире, чем у прочих светлых: от фона до карточки 1,23 против
   1,14 у «Дня». Лист заметно отделён от карточек, но ступени между
   полотном, колонкой и карточкой те же, что везде. */
[data-theme='ultraviolet'] {
  --bg: #e5e2f2;
  --surface: #f8f7fc;
  --surface-2: #dedaee;
  --surface-3: #d5d1e9;
  --canvas: #f3f2fa;
  --raised: #fbfaff;
  --well: #ebe9f5;
  --border: #e0ddef;
  --border-strong: #c0b9dc;
  --text: #1a1626;
  --text-dim: #4f4a66;
  --text-faint: #837e99;

  --accent: #5b3ee0;
  --accent-hover: #4a2fc4;
  --accent-soft: #ebe6fb;
  --accent-text: #ffffff;
  --accent-ring: rgba(91, 62, 224, .20);

  --ok: #2b7a4b;
  --ok-soft: #e2f0e7;
  --warn: #8a5d0f;
  --warn-soft: #f5ecd9;
  --critical: #c0392b;
  --critical-soft: #f8e6e3;

  --bar: #7857e8;
  --bar-done: #4a2fc4;
  --summary: #3d3654;
  --milestone: #8a5d0f;
  --off: #dedaee;
  --off-holiday: #f3e4e2;
  --today: #c0392b;

  --shadow-xs: 0 1px 2px rgba(38, 28, 78, .05);
  --shadow: 0 1px 3px rgba(38, 28, 78, .07);
  --shadow-md: 0 6px 18px rgba(38, 28, 78, .11);
  --shadow-lg: 0 18px 44px rgba(38, 28, 78, .18);
  --accent-grad: linear-gradient(135deg, #6d4cea 0%, #5b3ee0 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(91, 62, 224, .14) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(91, 62, 224, .06) 0%, transparent 60%);
}

/* ── Светлая «Gray Slate» — алебастр и грифельно-синий ────────────────────
   Alabaster Grey #E5E4E2 · Onyx #0A0A0A · Blue Slate #536878
   Светлая половина той же палитры, что и тёмный Onyx: фон алебастровый,
   текст оникс, акцент грифельный. Ни одного тёплого оттенка. */
[data-theme='slate'] {
  --bg: #dcdbd8;
  --surface: #eeedeb;
  --surface-2: #dcdbd8;
  --surface-3: #c0bfbb;
  /* Полотно на ступень ниже карточки, карточка — белая точка темы */
  --canvas: #edece9;
  --raised: #f5f4f1;
  --well: #e4e4e0;
  --border: #d0cfcc;
  --border-strong: #a3a29e;
  --text: #0a0a0a;
  --text-dim: #55565a;
  --text-faint: #86878b;

  --accent: #536878;
  --accent-hover: #3f4f5c;
  --accent-soft: #dfe6ea;
  --accent-text: #ffffff;
  --accent-ring: rgba(83, 104, 120, .22);

  --ok: #2f7a55;
  --ok-soft: #e3efe8;
  --warn: #8a6410;
  --warn-soft: #f3eddc;
  --critical: #b03a30;
  --critical-soft: #f6e6e4;

  --bar: #8298a8;
  --bar-done: #536878;
  --summary: #3d3e42;
  --milestone: #8a6410;
  --off: #cfcecb;
  --off-holiday: #eee0de;
  --today: #b03a30;

  --shadow-xs: 0 1px 2px rgba(10, 10, 10, .05);
  --shadow: 0 1px 3px rgba(10, 10, 10, .07);
  --shadow-md: 0 6px 18px rgba(10, 10, 10, .11);
  --shadow-lg: 0 18px 44px rgba(10, 10, 10, .18);
  --accent-grad: linear-gradient(135deg, #63798a 0%, #536878 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(83, 104, 120, .26) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(10, 10, 10, .08) 0%, transparent 60%);
}

/* ── Тёмная «Синяя ночь» — тёмно-синяя, как ночное окно ─────────────────── */
[data-theme='night'] {
  --bg: #0e1621;
  --surface: #17212b;
  --surface-2: #202b36;
  --surface-3: #2b3846;
  /* В темноте полотну вниз некуда: оно и так почти сливается с фоном,
     а карточка на нём читается по рамке. Ступени здесь нет. */
  --canvas: #17212b;
  --raised: #1c2731;
  --well: #111a24;
  --border: #232f3b;
  --border-strong: #3a4a5a;
  --text: #eef2f6;
  --text-dim: #91a4b7;
  --text-faint: #6b7f92;

  --accent: #64b5ef;
  --accent-hover: #8ccbf5;
  --accent-soft: #1e3348;
  --accent-text: #0b1219;
  --accent-ring: rgba(100, 181, 239, .24);

  --ok: #5fc78e;
  --ok-soft: #133026;
  --warn: #e0aa5a;
  --warn-soft: #33280f;
  --critical: #ec7a72;
  --critical-soft: #38201f;

  --bar: #4b8ac4;
  --bar-done: #64b5ef;
  --summary: #91a4b7;
  --milestone: #e0aa5a;
  --off: #131d28;
  --off-holiday: #2a1e21;
  --today: #ec7a72;

  /* В темноте тень почти не работает: слой поднимает светлота поверхности,
     а не чернота под ней. Поэтому тени слабее, чем на светлой стороне */
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .28);
  --shadow: 0 1px 3px rgba(0, 0, 0, .34);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .42);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .52);
  --accent-grad: linear-gradient(135deg, #8fc0f0 0%, #6ea8de 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(110, 168, 222, .20) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(60, 90, 140, .12) 0%, transparent 60%);
}

/* ── Тёмная «Графит» — нейтральная, без синевы ──────────────────────────── */
[data-theme='graphite'] {
  --bg: #17181b;
  --surface: #202226;
  --surface-2: #2a2d32;
  --surface-3: #35393f;
  /* В темноте полотну вниз некуда: оно и так почти сливается с фоном,
     а карточка на нём читается по рамке. Ступени здесь нет. */
  --canvas: #202226;
  --raised: #25272c;
  --well: #1a1b1f;
  --border: #2c2f35;
  --border-strong: #464a52;
  --text: #e9eaec;
  --text-dim: #a0a4ab;
  --text-faint: #74787f;

  --accent: #94a7bb;
  --accent-hover: #adbdcd;
  --accent-soft: #24303c;
  --accent-text: #14161a;
  --accent-ring: rgba(148, 167, 187, .24);

  --ok: #63b98d;
  --ok-soft: #182b22;
  --warn: #d7a860;
  --warn-soft: #2e2716;
  --critical: #e2807a;
  --critical-soft: #332120;

  --bar: #5b8cba;
  --bar-done: #94a7bb;
  --summary: #a0a4ab;
  --milestone: #d7a860;
  --off: #1b1d20;
  --off-holiday: #2b2020;
  --today: #e2807a;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .3);
  --shadow: 0 1px 3px rgba(0, 0, 0, .36);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .44);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .54);
  --accent-grad: linear-gradient(135deg, #adbdcd 0%, #94a7bb 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(121, 170, 218, .16) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(90, 100, 115, .10) 0%, transparent 60%);
}

/* ── Тёмная «Onyx» — уголь, грифельно-синий, алебастр ─────────────────────
   Onyx #0A0A0A · Blue Slate #536878 · Alabaster Grey #E5E4E2
   Самая тёмная и самая тихая. Фон почти чёрный, поэтому поверхности идут
   заметными ступенями: без них карточка на таком фоне не читается как
   карточка. Текст алебастровый, а не белый: чистый белый на угле светится. */
[data-theme='onyx'] {
  --bg: #0a0a0b;
  --surface: #141517;
  --surface-2: #1d1f22;
  --surface-3: #282b2f;
  /* В темноте полотну вниз некуда: оно и так почти сливается с фоном,
     а карточка на нём читается по рамке. Ступени здесь нет. */
  --canvas: #141517;
  --raised: #1a1c1f;
  --well: #0a0b0c;
  --border: #212326;
  --border-strong: #3c4147;
  --text: #e5e4e2;
  --text-dim: #9ca3ab;
  --text-faint: #6e757d;

  --accent: #8fb0c9;
  --accent-hover: #a8c3d8;
  --accent-soft: #1b242c;
  --accent-text: #0a0a0b;
  --accent-ring: rgba(143, 176, 201, .26);

  --ok: #63b98d;
  --ok-soft: #14251d;
  --warn: #d7a860;
  --warn-soft: #2a2415;
  --critical: #e2807a;
  --critical-soft: #2f1e1d;

  --bar: #536878;
  --bar-done: #8fb0c9;
  --summary: #9ca3ab;
  --milestone: #d7a860;
  --off: #0f1011;
  --off-holiday: #241a1a;
  --today: #e2807a;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow: 0 1px 3px rgba(0, 0, 0, .46);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .54);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .64);
  --accent-grad: linear-gradient(135deg, #a8c3d8 0%, #8fb0c9 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(83, 104, 120, .38) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(229, 228, 226, .05) 0%, transparent 60%);
}

/* ── Тёмная «Vivid Violet» — обсидиан, фиолет, абрикос ────────────────────
   Obsidian Veil #0B130E · Vivid Violet #9932CC · Apricot Glow #FFDAB9
   Фиолет взят из палитры без правок: белая подпись на #9932CC даёт 4.9:1,
   гасить его незачем. Текст сливочный, а не серый, — от этого экран
   не выглядит выключенным, и абрикос из палитры работает как тёплая
   отметка на сроках. */
[data-theme='violet'] {
  --bg: #0b130e;
  --surface: #121b15;
  --surface-2: #1a251d;
  --surface-3: #243026;
  /* В темноте полотну вниз некуда: оно и так почти сливается с фоном,
     а карточка на нём читается по рамке. Ступени здесь нет. */
  --canvas: #121b15;
  --raised: #18211a;
  --well: #0b130e;
  --border: #1e2a21;
  --border-strong: #3a4a3e;
  --text: #efe4d8;
  --text-dim: #a99c8f;
  --text-faint: #7b7166;

  --accent: #9932cc;
  /* Светлее, чем было (#b04ee0): этим цветом набран ярлык проекта на своей
     же мягкой заливке, и там он давал 4,04 — ниже порога 4,5, за которым
     мелкий текст перестаёт считаться читаемым. Тон тот же, поднята
     светлота; на 4,93 запас небольшой, но честный. */
  --accent-hover: #bd63e8;
  --accent-soft: #241730;
  --accent-text: #ffffff;
  --accent-ring: rgba(153, 50, 204, .32);

  --ok: #6ec08e;
  --ok-soft: #13251a;
  --warn: #ffdab9;
  --warn-soft: #2b2117;
  --critical: #e2807a;
  --critical-soft: #2f1d1c;

  --bar: #7a29a3;
  --bar-done: #9932cc;
  --summary: #a99c8f;
  --milestone: #ffdab9;
  --off: #0e170f;
  --off-holiday: #251a1a;
  --today: #e2807a;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .38);
  --shadow: 0 1px 3px rgba(0, 0, 0, .44);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .52);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .62);
  --accent-grad: linear-gradient(135deg, #b04ee0 0%, #9932cc 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(153, 50, 204, .40) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(255, 218, 185, .10) 0%, transparent 60%);
}

/* ── Тёмная «Prussian Blue» — берлинская лазурь и циан ────────────────────
   Prussian Blue #050A30 · Blue #0000FF · Cyan #00FFFF
   Самая яркая тема набора. Циан в акценте взят почти в полную силу —
   и работает именно потому, что подпись на нём тёмная: белая на циане
   даёт 1.2:1 и не читается вовсе. Чистый #0000FF в интерфейсе не стоит
   нигде, кроме полос диаграммы: на тексте он режет глаз. */
[data-theme='prussian'] {
  --bg: #0a0f26;
  --surface: #101632;
  --surface-2: #1a2140;
  --surface-3: #262e52;
  /* В темноте полотну вниз некуда: оно и так почти сливается с фоном,
     а карточка на нём читается по рамке. Ступени здесь нет. */
  --canvas: #101632;
  --raised: #161c3b;
  --well: #0a0f26;
  --border: #1e2540;
  --border-strong: #384166;
  --text: #e6ecff;
  --text-dim: #9aa6d6;
  --text-faint: #6f7bab;

  --accent: #00e5ff;
  --accent-hover: #6af0ff;
  --accent-soft: #0d2447;
  --accent-text: #041028;
  --accent-ring: rgba(0, 229, 255, .30);

  --ok: #4fd39a;
  --ok-soft: #0b2a26;
  --warn: #ffc861;
  --warn-soft: #2c2413;
  --critical: #ff8a80;
  --critical-soft: #331a1c;

  --bar: #3b5bff;
  --bar-done: #00e5ff;
  --summary: #9aa6d6;
  --milestone: #ffc861;
  --off: #080c1e;
  --off-holiday: #2a1626;
  --today: #ff8a80;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow: 0 1px 3px rgba(0, 0, 0, .46);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .54);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .64);
  --accent-grad: linear-gradient(135deg, #6af0ff 0%, #00b8d4 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(0, 229, 255, .14) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(0, 0, 255, .16) 0%, transparent 60%);
}

/* ── Тёмная «Gold» — тёплый уголь и золото ────────────────────────────────
   Gold #FFD700 · Burgundy #800020 · Crimson Carrot #FF4500
   Была винной и оказалась ядовитой версией Dark Wine: два винных фона
   в одном наборе, и второй проигрывал первому во всём. Пересобрана на
   тёплом угле — вино осталось в полосах диаграммы, а золото, находка этой
   палитры, наконец не спорит с фоном. Подпись на золоте тёмная: белая
   даёт на нём 1.4:1 и не читается. */
[data-theme='gold'] {
  --bg: #16120c;
  --surface: #1f1a12;
  --surface-2: #2a241a;
  --surface-3: #372f23;
  /* В темноте полотну вниз некуда: оно и так почти сливается с фоном,
     а карточка на нём читается по рамке. Ступени здесь нет. */
  --canvas: #1f1a12;
  --raised: #262017;
  --well: #16120c;
  --border: #2b251b;
  --border-strong: #4a4030;
  --text: #f7e7d5;
  --text-dim: #bfae95;
  --text-faint: #8b7d68;

  --accent: #ffd700;
  --accent-hover: #ffe64d;
  --accent-soft: #3a2a05;
  --accent-text: #2a1002;
  --accent-ring: rgba(255, 215, 0, .30);

  --ok: #7bc47f;
  --ok-soft: #16290f;
  --warn: #ffb347;
  --warn-soft: #33220c;
  --critical: #ff4500;
  --critical-soft: #3a1305;

  --bar: #800020;
  --bar-done: #ffd700;
  --summary: #c0a08f;
  --milestone: #ffb347;
  --off: #120f0a;
  --off-holiday: #2a1710;
  --today: #ff4500;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .42);
  --shadow: 0 1px 3px rgba(0, 0, 0, .48);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .56);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .66);
  --accent-grad: linear-gradient(135deg, #ffe64d 0%, #e6c200 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(255, 215, 0, .12) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(128, 0, 32, .22) 0%, transparent 60%);
}


/* ── Светлая «Apricot» — абрикос и фиолет ─────────────────────────────────
   Apricot Glow #FFDAB9 · Obsidian Veil #0B130E · Vivid Violet #9932CC
   Светлая половина той же палитры, что и тёмный Vivid Violet: там фиолет
   светится на почти чёрном, здесь лежит на тёплом персиковом. Одна пара
   цветов, два совершенно разных экрана. */
[data-theme='apricot'] {
  --bg: #f0dfd2;
  --surface: #faf3ec;
  --surface-2: #e6d3c4;
  --surface-3: #dbc5b3;
  /* Полотно на ступень ниже карточки, карточка — белая точка темы */
  --canvas: #f7ebe1;
  --raised: #fcf3ec;
  --well: #f2e2d5;
  --border: #e8d8c9;
  --border-strong: #c4ac99;
  --text: #2a1a10;
  --text-dim: #6b5344;
  --text-faint: #9a8271;

  --accent: #9932cc;
  --accent-hover: #7f27ab;
  --accent-soft: #f4e7fa;
  --accent-text: #ffffff;
  --accent-ring: rgba(153, 50, 204, .22);

  --ok: #3f7a52;
  --ok-soft: #e7f0e7;
  --warn: #96690f;
  --warn-soft: #f8eddb;
  --critical: #b03a30;
  --critical-soft: #f9e8e5;

  --bar: #d9a6e8;
  --bar-done: #9932cc;
  --summary: #4a3a2e;
  --milestone: #96690f;
  --off: #e6d3c4;
  --off-holiday: #f7dcd6;
  --today: #b03a30;

  --shadow-xs: 0 1px 2px rgba(90, 55, 30, .06);
  --shadow: 0 1px 3px rgba(90, 55, 30, .08);
  --shadow-md: 0 6px 18px rgba(90, 55, 30, .12);
  --shadow-lg: 0 18px 44px rgba(90, 55, 30, .20);
  --accent-grad: linear-gradient(135deg, #b04ee0 0%, #9932cc 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(153, 50, 204, .16) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(255, 218, 185, .45) 0%, transparent 60%);
}

/* ── Тёмная «Dark Wine» — вино, лён, шалфей ───────────────────────────────
   Dark Wine #6F1D1B · Linen #F0E5DE · Ash Grey #ADBDAB
   Тёмная половина палитры Linen. Акцент здесь не винный, а шалфейный:
   вино ушло в фон, и кнопка того же цвета на нём пропала бы. Зелёный
   на винном — самое неожиданное сочетание набора и самое спокойное. */
[data-theme='darkwine'] {
  --bg: #1b0d0c;
  --surface: #251312;
  --surface-2: #321a18;
  --surface-3: #422322;
  /* В темноте полотну вниз некуда: оно и так почти сливается с фоном,
     а карточка на нём читается по рамке. Ступени здесь нет. */
  --canvas: #251312;
  --raised: #2f1817;
  --well: #1b0d0c;
  --border: #321a18;
  --border-strong: #5c3634;
  --text: #f0e5de;
  --text-dim: #bda79e;
  --text-faint: #8d7a72;

  --accent: #adbdab;
  --accent-hover: #c2cfc0;
  --accent-soft: #232e23;
  --accent-text: #1b0d0c;
  --accent-ring: rgba(173, 189, 171, .28);

  --ok: #8bc48f;
  --ok-soft: #172a18;
  --warn: #e0b070;
  --warn-soft: #2e2416;
  --critical: #ff6f61;
  --critical-soft: #3a1a17;

  --bar: #8e2b28;
  --bar-done: #adbdab;
  --summary: #bda79e;
  --milestone: #e0b070;
  --off: #160a09;
  --off-holiday: #2e1310;
  --today: #ff6f61;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .42);
  --shadow: 0 1px 3px rgba(0, 0, 0, .48);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .56);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .66);
  --accent-grad: linear-gradient(135deg, #c2cfc0 0%, #adbdab 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(111, 29, 27, .85) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(173, 189, 171, .10) 0%, transparent 60%);
}

/* ── Тёмная «Inferno» — ночь, барвинок, инферно ───────────────────────────
   Bright Snow #FAFBFD · Inferno #AA0003 · Periwinkle #BFB4DC
   Тёмная половина палитры Periwinkle, и здесь барвинок наконец стоит в
   акценте таким, какой он есть: на светлом фоне он не несёт текста, а на
   ночном несёт — с тёмной подписью. Инферно остался тревожным цветом. */
[data-theme='inferno'] {
  --bg: #15131f;
  --surface: #1d1a2b;
  --surface-2: #262236;
  --surface-3: #322c46;
  /* В темноте полотну вниз некуда: оно и так почти сливается с фоном,
     а карточка на нём читается по рамке. Ступени здесь нет. */
  --canvas: #1d1a2b;
  --raised: #232033;
  --well: #15131f;
  --border: #2a2639;
  --border-strong: #443e5c;
  --text: #fafbfd;
  --text-dim: #a8adc4;
  --text-faint: #7a7f96;

  --accent: #bfb4dc;
  --accent-hover: #d3cbe8;
  --accent-soft: #23213a;
  --accent-text: #101018;
  --accent-ring: rgba(191, 180, 220, .30);

  --ok: #63b98d;
  --ok-soft: #14251d;
  --warn: #e0b070;
  --warn-soft: #2b2417;
  --critical: #ff4d4f;
  --critical-soft: #3a1416;

  --bar: #7a6bb5;
  --bar-done: #bfb4dc;
  --summary: #a8adc4;
  --milestone: #e0b070;
  --off: #111020;
  --off-holiday: #2a1518;
  --today: #ff4d4f;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow: 0 1px 3px rgba(0, 0, 0, .46);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .54);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .64);
  --accent-grad: linear-gradient(135deg, #d3cbe8 0%, #bfb4dc 100%);
  --bg-grad: radial-gradient(90% 65% at 8% -12%, rgba(170, 0, 3, .16) 0%, transparent 58%), radial-gradient(80% 60% at 100% 108%, rgba(191, 180, 220, .10) 0%, transparent 60%);
}
/* Сторона света отдельно от темы: браузеру — какие рисовать поля ввода
   и полосы прокрутки, приложению — на что опираться там, где важно не
   «какая тема», а «светло или темно» */
[data-scheme='light'] { color-scheme: light; }
[data-scheme='dark']  { color-scheme: dark; }

/* ── Крупность интерфейса ────────────────────────────────────────────────
   Весь текст задан в rem, поэтому масштаб меняется здесь одной строкой.
   На большом мониторе интерфейс крупнее, на телефоне чуть мельче:
   расстояние до экрана разное. */
html { font-size: 16px; }

* { box-sizing: border-box; }
/* Атрибут hidden должен работать и на кнопках: у них свой display,
   который иначе перебивает правило браузера */
[hidden] { display: none !important; }
html, body, #root { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--t-base);
  line-height: 1.5;
  color: var(--text);
  /* Свечение из верхнего угла — то, за счёт чего экран перестаёт быть
     равномерной заливкой. Оно закреплено (fixed) и не едет при прокрутке:
     ползущее пятно за текстом читается как дефект отрисовки. Держать его
     едва заметным обязательно — фон под текстом не должен быть событием. */
  background: var(--bg);
  background-image: var(--bg-grad);
  background-attachment: fixed;
  background-repeat: no-repeat;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: 'cv02', 'cv03', 'cv04', 'tnum';
  overscroll-behavior: none;
  position: fixed; inset: 0;      /* никакой прокрутки самой страницы */
  width: 100%;
}

/* Заголовки плотнее текста: так выстраивается иерархия без лишних размеров */
h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.021em; line-height: 1.25; }
h1 { font-size: var(--t-2xl); }
h2 { font-size: var(--t-lg); }
h3 { font-size: var(--t-md); }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
input, select, textarea, button { font: inherit; color: inherit; }
code { font-family: var(--mono); font-size: .92em; }

/* Кольцо фокуса одно на всё приложение — видно, куда попала клавиатура */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }

::selection { background: var(--accent-soft); color: var(--accent); }

/* Полосы прокрутки: тонкие, проявляются при наведении */
/* Полосы прокрутки: тонкие, проявляются при наведении.
   
   Тонкость задаётся двумя разными способами, и это не перестраховка.
   `scrollbar-width` — стандартное свойство, но в Chrome оно СИЛЬНЕЕ правил
   `::-webkit-scrollbar-*`: как только оно задано, весь набор веб-китовых
   правил ниже перестаёт применяться целиком. Мы этого не знали и полдня
   правили дорожку, которая ни на что не влияла: полоса выходила 12 пикселей
   вместо просимых 10, со стрелками на концах и без отступов.
   
   Поэтому стандартное свойство включается только там, где веб-китовых
   псевдоэлементов нет вовсе, — то есть в Firefox. Chrome и Safari получают
   правила ниже и вместе с ними отступ от скруглённых углов полотна. */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: var(--radius-full);
  border: 3px solid transparent; background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: content-box; }
/* Кнопки-стрелки на концах полосы. Их рисует система, и в нашем интерфейсе
   их не нажимают ни разу: листают колесом и перетаскиванием. Зато они
   занимают концы дорожки — и отступ, который отводит полосу от скруглённого
   угла, до неё не доходил. */
::-webkit-scrollbar-button { display: none; width: 0; height: 0; }

/* Полотно-остров скруглено на 18 пикселей, и полоса прокрутки шла по самому
   его краю: сверху и снизу она входила прямо в закругление, и между дугой
   рамки и прямым столбиком полосы оставался клин — угол выглядел срезанным.
   Отводим дорожку от концов ровно на радиус: полоса начинается там, где
   закругление заканчивается. */
.main::-webkit-scrollbar-track { margin: var(--radius-xl) 0; }

/* ── Элементы управления ─────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  height: 36px; padding: 0 var(--s-4); border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--surface);
  cursor: pointer; white-space: nowrap; font-weight: 500; font-size: var(--t-base);
  color: var(--text); box-shadow: var(--shadow-xs);
  transition: background var(--fast), border-color var(--fast),
              box-shadow var(--fast), transform .08s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.btn:hover { background: var(--surface-2); border-color: var(--text-faint); }
.btn:active { transform: scale(.97); }
.btn:disabled { opacity: .45; cursor: default; transform: none; box-shadow: none; }

/* Главная кнопка залита градиентом внутри своего же цвета — не радугой:
   переход даёт объём, а смена оттенка превратила бы кнопку в наклейку.
   Плоский --accent остаётся подложкой на случай, если градиент не задан. */
.btn--primary {
  background: var(--accent); background-image: var(--accent-grad);
  border-color: var(--accent); color: var(--accent-text);
  box-shadow: var(--shadow-xs), inset 0 1px 0 rgba(255, 255, 255, .18);
}
.btn--primary:hover {
  background: var(--accent-hover); background-image: none; border-color: var(--accent-hover);
}
.btn--ghost { border-color: transparent; background: transparent; box-shadow: none; }
.btn--ghost:hover { background: var(--surface-2); border-color: transparent; }
.btn--danger { color: var(--critical); }
.btn--danger:hover { background: var(--critical-soft); border-color: var(--critical); }
.btn--sm { height: 30px; padding: 0 var(--s-3); font-size: var(--t-sm); border-radius: var(--radius-sm); }
.btn--icon { width: 36px; padding: 0; }
.btn--sm.btn--icon { width: 30px; }

.field { display: block; margin-bottom: var(--s-4); }
.field__label {
  display: block; font-size: var(--t-sm); color: var(--text-dim);
  margin-bottom: var(--s-2); font-weight: 500;
}
.input, .select, .textarea {
  width: 100%; height: 38px; padding: 0 var(--s-3);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); outline: none; color: var(--text);
  transition: border-color var(--fast), box-shadow var(--fast);
}
.input::placeholder, .textarea::placeholder { color: var(--text-faint); }
.input:hover, .select:hover, .textarea:hover { border-color: var(--text-faint); }
.textarea { height: auto; padding: var(--s-3) var(--s-3); min-height: 80px; resize: vertical; line-height: 1.55; }
/* Список задач: строк сразу десять, чтобы вставленное было видно целиком,
   а не в щель на три строки. Межстрочный интервал плотнее обычного текста:
   здесь не абзац, а перечень, и строки читаются как ряд. */
.spisok__text { min-height: 210px; line-height: 1.45; }
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring);
}
.input:disabled, .select:disabled, .textarea:disabled {
  background: var(--surface-2); color: var(--text-dim); cursor: default;
}
/* Стрелка выпадающего списка рисуется фоном: свой значок вместо системного */
.select {
  appearance: none; padding-right: var(--s-6); cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
                    linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
  background-position: calc(100% - 16px) 17px, calc(100% - 11px) 17px;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}
.row { display: flex; gap: var(--s-3); }
.row > * { flex: 1; min-width: 0; }

.chip {
  display: inline-flex; align-items: center; gap: var(--s-1); height: 24px; padding: 0 var(--s-3);
  border-radius: var(--radius-full); font-size: var(--t-sm); font-weight: 500;
  background: var(--surface-2); color: var(--text-dim); border: 1px solid transparent;
  white-space: nowrap;
}
.chip--org { background: var(--accent-soft); color: var(--accent); }
.chip--ok { background: var(--ok-soft); color: var(--ok); }
.chip--warn { background: var(--warn-soft); color: var(--warn); }
/* Просрочка — один и тот же знак во всех местах: на главной, в списке
   проектов, в загрузке людей и в поиске. Раньше тот же факт бывал и серой
   строкой меты, и красной датой, и не показывался вовсе. */
.chip--overdue { font-weight: 600; }
.chip--xs { height: 20px; padding: 0 var(--s-2); font-size: var(--t-2xs); }
.peoplemeta { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.chip--danger { background: var(--critical-soft); color: var(--critical); }

.note { font-size: var(--t-sm); color: var(--text-dim); line-height: 1.55; }
.error {
  background: var(--critical-soft); color: var(--critical);
  padding: var(--s-3) var(--s-3); border-radius: var(--radius-sm); font-size: var(--t-base);
  margin-bottom: var(--s-3); border: 1px solid transparent;
}
.empty { text-align: center; padding: var(--s-7) var(--s-5); color: var(--text-dim); }
.empty h3 { margin-bottom: var(--s-2); color: var(--text); }

.card {
  background: var(--raised); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-xs);
}
/* Список прямо на полотне: рамка вокруг него на том же цвете ничего не отделяет,
   а только добавляет ещё одну коробку. Строки разделяет волосяная линия. */
.card--plain {
  background: transparent; border: none; box-shadow: none; border-radius: 0;
}
.card + .card { margin-top: var(--s-4); }
.card__head {
  padding: var(--s-4) var(--s-4); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: var(--s-3);
}
.card__body { padding: var(--s-4); }

/* Вкладки: подчёркивание бежит за активной, без рамок и заливок */
.tabs {
  display: flex; gap: var(--s-05); border-bottom: 1px solid var(--border);
  margin-bottom: var(--s-4);
  /* Вкладки листаются вбок, если их много. Вертикаль закрываем явно: вкладка
     заходит на границу на пиксель (margin-bottom: -1px), и браузер из-за этого
     пиксела рисовал справа вертикальную полосу прокрутки. */
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  padding: var(--s-2) var(--s-3); border: none; background: none; cursor: pointer;
  color: var(--text-dim); font-weight: 500; font-size: var(--t-base);
  border-bottom: 2px solid transparent; white-space: nowrap; margin-bottom: -1px;
  transition: color var(--fast), border-color var(--fast);
}
.tab:hover { color: var(--text); }
.tab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
/* Число рядом с названием вкладки: сколько там строк, видно не заходя */
.tab__n {
  margin-left: var(--s-2); color: var(--text-faint); font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
}
/* Вкладка, которую видит не каждый: точка слева говорит «это ваши права»,
   а не «так у всех». Слов не пишем — они заняли бы полполосы. */


.table { width: 100%; border-collapse: collapse; font-size: var(--t-base); font-variant-numeric: tabular-nums; }
.table th {
  text-align: left; font-weight: 500; color: var(--text-dim); font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: .04em;
  padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--border); white-space: nowrap;
}
.table td { padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--border); vertical-align: middle; }
/* Числа в таблицах — по правому краю и моноширинные: только так разряды
   встают друг под друга и суммы можно сравнить глазом. Разметка помечает
   такие ячейки классом num. */
.table th.num, .table td.num {
  text-align: right;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: -.01em; white-space: nowrap;
}
.table th.num { font-family: inherit; }
.table tr:last-child td { border-bottom: none; }
.table tbody tr { transition: background var(--fast); }
.table tbody tr:hover td { background: var(--surface-2); }
.table__actions { display: flex; gap: var(--s-2); justify-content: flex-end; }

/* ── Каркас приложения ───────────────────────────────────────────────────── */

/* Высота ровно по экрану, с учётом «чёлки» и полосы жестов на айфоне.
   Без этого в ярлыке с рабочего стола низ уезжает под системную полосу,
   а страница начинает пружинить вместо прокрутки. */
.app {
  display: flex; flex-direction: column;
  /* body закреплён (position: fixed; inset: 0), поэтому 100% — это ровно
     видимая область. -webkit-fill-available здесь давал неверную высоту. */
  height: 100%;
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  overflow: hidden;
}

.topbar {
  display: flex; align-items: center; gap: var(--s-3);
  height: 60px; padding: 0 var(--s-5); flex: none;
  /* Шапка лежит на общем фоне без заливки и линии: заливка с бортиком делала
     из неё полку, а из навигации — колонку. Границы рисует не линия, а воздух. */
  background: transparent;
}
/* Логотип занимает ровно ширину боковой колонки: разделы в шапке начинаются
   там же, где начинается полотно с содержимым, и экран не режется вертикалью
   посередине пустого места */
.topbar__brand {
  display: flex; align-items: center; gap: var(--s-2);
  font-weight: 600; letter-spacing: -.02em; font-size: var(--t-md);
  flex: 0 0 calc(var(--side-w) - var(--s-5) - var(--s-3)); min-width: 0;
}
/* Логотип бюро вместо набранного имени. Маской, а не картинкой: так знак
   красится currentColor и одинаково жив во всех шести темах — иначе пришлось
   бы держать чёрный файл и белый и переключать их темой.

   Квадратного знака рядом больше нет: либо знак с набранным именем, либо
   логотип — вместе это две подписи об одном. Логотип поэтому крупнее, чем
   был в паре со знаком: он теперь один держит левый край шапки.
   Высота долей кегля, а не ступенью шкалы: это доля кегля, а не размер. */
.brand__name {
  display: block; height: .95em; aspect-ratio: 1527.85 / 147.62;
  background-color: currentColor;
  -webkit-mask: url(img/logo-linesmatter.svg) no-repeat center / contain;
  mask: url(img/logo-linesmatter.svg) no-repeat center / contain;
}
/* Знак «Стадии»: три линии убывающей толщины — концепция, проект, рабочая
   документация.

   В шапке и на входе он больше не рисуется: у бюро там логотип, а знак
   с логотипом рядом — две подписи об одном. Правило оставлено намеренно,
   а не забыто: знак живёт в иконках приложения, и другому бюро, которому
   логотипа взять неоткуда, шапка со знаком и набранным именем вернётся
   одной строкой в `brandName()`. Пропорции взяты из мелкого построения знака (32px и меньше),
   а не из крупного: у крупного тонкая линия на 26 пикселях пропадает совсем.
   В долях стороны: поле 22%, длина штриха 56%, толщины 16 / 8,5 / 4,5%.

   Красится темой. Плитка — акцент, штрихи — цвет подписи на акценте, то есть
   ровно та пара, что уже проверена на контраст в каждой из шестнадцати тем.
   Знак получается самым ярким предметом в шапке — так и задумано: это
   единственное место, где цвет темы стоит в полную силу и ничего не значит,
   кроме «вот приложение». */
.topbar__logo {
  width: 26px; height: 26px; border-radius: 5.7px; flex: none;
  background-color: var(--accent);
  /* Три штриха — тремя слоями заливки, а не псевдоэлементами: их и нужно три,
     а псевдоэлементов у узла два. Размеры в пикселях, а не в долях: проценты
     в background-position считаются от разницы размеров, и штрих уезжает
     тем сильнее, чем он тоньше. Знак везде 26 пикселей, так что это точно.
     Доли построения: поле 22%, длина 56%, толщины 16 / 8,5 / 4,5%. */
  background-image:
    linear-gradient(var(--accent-text), var(--accent-text)),
    linear-gradient(var(--accent-text), var(--accent-text)),
    linear-gradient(var(--accent-text), var(--accent-text));
  background-repeat: no-repeat;
  background-size: 14.6px 4.2px, 14.6px 2.2px, 14.6px 1.2px;
  background-position: 5.7px 6.1px, 5.7px 13.4px, 5.7px 18.7px;
  box-shadow: 0 1px 3px var(--accent-ring);
}
.topbar__spacer { flex: 1; }
.topbar__title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar__user { display: flex; align-items: center; gap: var(--s-2); }
.avatar {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  display: grid; place-items: center; font-size: var(--t-xs); font-weight: 600;
  background: var(--accent-soft); color: var(--accent);
  letter-spacing: .01em;
}
.badge {
  min-width: 18px; height: 18px; padding: 0 var(--s-1); border-radius: var(--radius);
  background: var(--critical); color: #fff; font-size: var(--t-2xs); font-weight: 600;
  display: inline-grid; place-items: center; line-height: 1;
}
/* Метка словом, а не числом: она стоит в строке и должна читаться */
.badge--bad, .badge--warn { height: auto; padding: 2px var(--s-2); font-weight: 500; }
.badge--warn { background: var(--warn); color: var(--text); }

.main {
  flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* Содержимое — отдельное полотно, лежащее на фоне приложения. Скругление и
     зазор по краям делают то, что раньше делала граница у навигации.
     Заливка — своя, --canvas: полотно и лежащая на нём карточка не могут
     быть одного цвета, иначе карточки на нём не видно (см. раздел тем). */
  background: var(--canvas);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  margin: 0 var(--s-5) var(--s-5) 0;
}
.page {
  max-width: min(1440px, 100%); margin: 0 auto;
  /* предел ширины держит строку читаемой; холсты снимают его через .page--wide */
  /* Поле сверху срезано на пятую часть: над названием проекта стояла пустая
     полоса в треть высоты шапки, а под ней сразу начинается работа. На
     широком экране это 27 пикселей вместо 35 — заметно, но не в упор. */
  padding: clamp(20px, 1.9vw, 32px) clamp(20px, 2.2vw, 36px) var(--s-7);
}
/* Экран с доской: гант, канбан и таблица — холсты, им нужен весь монитор.
   Ограничивать их шириной колонки текста значит терять недели на шкале. */
.page--wide { max-width: 100%; }

/* Свернул колонку — получил ширину.
   Предел в 1440 держит строку читаемой, и убирать его совсем нельзя: список
   во всю ширину большого монитора читается хуже, а не лучше. Но человек,
   свернувший колонку, сделал это ради места, и не дать ему ничего — значит
   не выполнить того, о чём он попросил. Ступень одна и небольшая. */
.shell:has(.side.is-folded) .page,
.shell:has(.side--bare) .page { max-width: min(1680px, 100%); }
/* …но не холстам. Ступень выше придумана для страницы с текстом; для доски
   и справочника она не прибавка, а отнятие: у них предела нет вовсе, и
   правило про свёрнутую колонку возвращало им 1680 — то есть раздел без
   левой колонки оказывался уже, чем такой же с колонкой. */
.shell:has(.side.is-folded) .page--wide,
.shell:has(.side--bare) .page--wide { max-width: 100%; }
.shell:has(.side.is-folded) .page--roomy,
.shell:has(.side--bare) .page--roomy { max-width: min(1760px, 100%); }
.shell:has(.side.is-folded) .page--home,
.shell:has(.side--bare) .page--home { max-width: min(1920px, 100%); }

/* Главная: список работы плюс колонка заметок в 316 точек.
   Предел в 1440 придуман для строки текста, а здесь его делят надвое — и на
   широком мониторе список задач оставался в тысяче точек, канбан ужимался
   до четырёх узких колонок с прокруткой вбок, а справа от заметок стояло
   пустое поле в треть экрана. Ступень поднята ровно настолько, чтобы
   заметки остались на месте, а работе досталось освободившееся. */
.page--home { max-width: min(1920px, 100%); }

/* Экраны, где содержимое — сетка карточек, а не текст.
   Предел в 1440 придуман для строки: она читается тем хуже, чем длиннее.
   Сетке он ни к чему — от лишней ширины в ней появляется ещё один столбец,
   то есть ещё три проекта на виду. Но и совсем без предела нельзя: на очень
   широком мониторе ряд из шести карточек глазом уже не охватывается. */
.page--roomy { max-width: min(1760px, 100%); }

/* Возврат к списку — стрелкой в самом заголовке, а не отдельной строкой крошек:
   раздел и так подсвечен в навигации, дублировать его строкой нечем оправдать */
.backbtn {
  width: 30px; height: 30px; flex: none; display: grid; place-items: center;
  border: none; background: none; cursor: pointer; border-radius: var(--radius-sm);
  color: var(--text-faint); font-size: var(--t-lg); font-family: inherit;
  transition: background var(--fast), color var(--fast);
}
.backbtn:hover { background: var(--surface-2); color: var(--text); }

/* Команда проекта в шапке: аватары стопкой и вход в состав. Держим её на виду,
   потому что без людей в проекте задачу не на кого назначить. */
/* Люди и «плюс» — в одной рамке: раньше плюс стоял отдельной кнопкой рядом
   с обведённым рядом лиц, и было непонятно, к чему он относится. Теперь он
   выглядит как следующий по счёту человек, которого ещё нет. */
.teamwrap {
  display: inline-flex; align-items: center; gap: var(--s-1); flex: none;
  height: 40px; padding: 0 var(--s-2); border-radius: var(--radius-full);
  border: 1px solid var(--border); background: none;
  transition: border-color var(--fast);
}
.teamwrap:hover { border-color: var(--border-strong); }
/* Отдельная кнопка «плюс»: показывает, кого можно добавить, и добавляет
   одним нажатием — раньше за этим открывалось окно с двумя списками */
/* Тот же круг, что у лиц: «плюс» читается как следующий человек */
.teamadd {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  border: 1px dashed var(--border-strong); background: none; cursor: pointer;
  color: var(--text-dim); font-size: var(--t-lg); line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: var(--s-1);
  transition: border-color var(--fast), color var(--fast);
}
.teamadd:hover, .teamadd.is-open { border-color: var(--accent); color: var(--accent); }
.teambtn {
  display: inline-flex; align-items: center; flex: none;
  height: 34px; padding: 0; border: none; background: none; cursor: pointer;
  color: var(--text-dim); font-family: inherit; font-size: var(--t-sm);
  transition: color var(--fast);
}
.teambtn:hover { color: var(--accent); }
/* До шести человек лица стоят в ряд и не перекрываются: так видно, кто
   в проекте, не открывая ничего. Дальше ряд занял бы полшапки — лица
   наезжают друг на друга, а хвост сворачивается в «+N». */
.teambtn .avatar { margin-left: var(--s-1); border: 2px solid var(--surface); }
.teambtn.is-dense .avatar { margin-left: -8px; }
.teambtn .avatar:first-child { margin-left: 0; }
.avatar--team { width: 34px; height: 34px; font-size: var(--t-xs); }
.teambtn__rest {
  font-size: var(--t-2xs); color: var(--text-faint); margin-left: var(--s-1); font-weight: 600;
}
.teambtn__add { margin-left: var(--s-2); font-weight: 600; }

/* Окно новой задачи: узкое, полей ровно столько, сколько нужно на входе */
.modal--task { max-width: 420px; width: min(420px, calc(100vw - 32px)); }
/* Поле не распирает окно.

   На айфоне окно новой задачи уезжало правым краем за экран: у поля с датой
   в Safari своя наименьшая ширина, и оно раздвигало окно изнутри вместо
   того, чтобы ужаться. Проверить это в настольном браузере нельзя — там
   дата рисуется иначе, — поэтому правило поставлено на все поля разом:
   ширина по месту, а не по прихоти поля. */
.modal input, .modal select, .modal textarea {
  max-width: 100%; min-width: 0; box-sizing: border-box;
}
.modal input[type="date"] { -webkit-appearance: none; appearance: none; }
.modal__body { overflow-x: hidden; }

/* Отбивка под шапкой — на ступень меньше: вместе с урезанным полем сверху
   это отдаёт работе почти строку экрана, а ритма листа не ломает. */
.page__head { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-3); flex-wrap: wrap; }
/* Заголовок не растягивается на всю полосу: иначе всё, что стоит следом,
   уезжает к правому краю и перестаёт читаться вместе с названием */
.page__head h1 { min-width: 0; }
.page__head h1.page__title { flex: none; }
.page__head > .spacer { flex: 1; }
/* Правая часть шапки проекта переносится целиком, а не по кнопке: длинные
   названия разделов («Задачи по комплектации») забирают ширину, и раньше
   на вторую строку уезжала одна шестерёнка */
/* Прижата вправо своим полем, а не распоркой: с распоркой при переносе она
   съедала остаток первой строки, и группа начинала вторую от левого края —
   как будто это ещё одна строка содержимого, а не правый угол шапки */
.page__head-right {
  display: flex; align-items: center; gap: var(--s-2); flex: none; margin-left: auto;
}
/* Плашка-кнопка: выглядит как метка, но по ней есть выбор */
.chip--act { cursor: pointer; border: none; font: inherit; font-weight: 600; }
/* Тихая плашка: состояние дел, а не выделенная метка — без жирного и заливки */
.chip--quiet {
  background: none; color: var(--text-dim); font-weight: 400;
  border: 1px solid var(--border);
}
.chip--quiet:hover { border-color: var(--border-strong); color: var(--text); }
.chip--act:hover { filter: brightness(.96); }

/* ── Вход ────────────────────────────────────────────────────────────────── */

.auth { min-height: 100dvh; display: grid; place-items: center; padding: var(--s-5); }
.auth__box { width: 100%; max-width: 380px; }
.auth__logo { display: flex; align-items: center; gap: var(--s-3); justify-content: center; margin-bottom: var(--s-5); font-size: var(--t-lg); font-weight: 600; }
/* На входе логотип крупнее, чем в шапке: там он метка рядом с разделами,
   а здесь единственное, что есть на экране до ввода пароля. Тонкие штрихи
   «MATTER» на невысоком кегле выцветают в полпикселя — экрану нужно дать
   им высоту, благо место на этой странице есть. */
.auth__logo .brand__name { height: .95em; }
.auth__switch { text-align: center; margin-top: var(--s-4); font-size: var(--t-base); color: var(--text-dim); }
.auth__switch button { background: none; border: none; color: var(--accent); cursor: pointer; padding: 0; font-weight: 500; }

/* ── Список досок ────────────────────────────────────────────────────────── */

/* Карточки покрупнее: при 268px на широком экране получалось четыре узких
   колонки, и пары проектов не хватало, чтобы ряд выглядел заполненным */
.boards { display: grid; gap: var(--s-3); grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }
.bcard {
  /* Колонкой, чтобы подвал с метками прижимался к низу: иначе карточки в ряду
     разной высоты и нижний край получается рваным */
  display: flex; flex-direction: column; text-align: left; width: 100%; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--s-4); box-shadow: var(--shadow-xs); color: var(--text);
  transition: border-color var(--fast), box-shadow var(--fast), transform var(--fast);
}
.bcard:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.bcard__title { font-weight: 600; margin-bottom: var(--s-1); }
.bcard__meta {
  font-size: var(--t-sm); color: var(--text-dim); margin-bottom: var(--s-2);
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
/* Описание режется по строкам, а не по символам: обрыв приходится на границу
   слова, а карточки в ряду получают одинаковую высоту */
.bcard__desc {
  font-size: var(--t-sm); color: var(--text-dim); line-height: 1.45;
  margin-bottom: var(--s-3);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.bcard__foot { display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; margin-top: auto; }
.group-title { margin: var(--s-5) 0 var(--s-3); display: flex; align-items: center; gap: var(--s-2); }
.group-title h2 { font-size: var(--t-md); }
.group-title .note { font-weight: 400; }

/* ── Экран доски ─────────────────────────────────────────────────────────── */

.board { display: flex; flex-direction: column; height: 100%; min-height: 0; }
/* Панель переносится на вторую строку, а не уезжает вбок: уехавшие кнопки
   просто исчезали с глаз — на 800 точках за краем оставался и переключатель
   вида, и отбор по типам. */
.board__bar {
  display: flex; align-items: center; gap: var(--s-2); padding: var(--s-3) var(--s-4); flex: none;
  background: var(--surface); border-bottom: 1px solid var(--border);
  flex-wrap: wrap; row-gap: var(--s-2);
}
.board__bar::-webkit-scrollbar { display: none; }
.board__bar > * { flex: none; }
.board__bar .spacer { flex: 1 1 auto; min-width: 4px; }
.seg {
  display: inline-flex; gap: var(--s-05); padding: var(--s-05);
  background: var(--surface-2); border-radius: var(--radius-sm);
}
.seg button {
  height: 28px; padding: 0 var(--s-3); flex: none; border: none; background: none;
  border-radius: var(--radius-sm); cursor: pointer; color: var(--text-dim);
  font-size: var(--t-sm); font-weight: 500; white-space: nowrap;
  transition: background var(--fast), color var(--fast);
}
.seg button:hover { color: var(--text); }
.seg button.is-active {
  background: var(--surface); color: var(--text); font-weight: 600;
  box-shadow: var(--shadow-xs);
}

/* ── Диаграмма ───────────────────────────────────────────────────────────── */

.gantt { position: relative; flex: 1; min-height: 0; background: var(--surface); }
.gantt__scroll { position: absolute; inset: 0; overflow: auto; overscroll-behavior: contain; }
.gantt__inner {
  display: grid;
  grid-template-columns: var(--left-w) var(--chart-w);
  grid-template-rows: auto 1fr;
  width: max-content; min-width: 100%; min-height: 100%;
}
.gantt__corner {
  position: sticky; left: 0; top: 0; z-index: 5;
  background: var(--surface); border-right: 1px solid var(--border-strong);
  border-bottom: 1px solid var(--border-strong);
}
.gantt__headright {
  position: sticky; top: 0; z-index: 3;
  background: var(--surface); border-bottom: 1px solid var(--border-strong);
}
.gantt__left {
  position: sticky; left: 0; z-index: 2; overflow: visible;
  background: var(--surface); border-right: 1px solid var(--border-strong);
}
.gantt__right { position: relative; }
.gantt.is-dragging { cursor: grabbing; user-select: none; }
.gantt.is-dragging .gantt__scroll { scroll-behavior: auto; }

.gantt__headtitle {
  height: 100%; display: flex; align-items: flex-end; padding: 0 var(--s-3) var(--s-2);
  font-size: var(--t-sm); font-weight: 600; color: var(--text-dim);
  gap: var(--s-2);
}
.gantt__headtitle-name { flex: 1; min-width: 0; }
/* Подпись над числами: раньше над ними стояло слово «Задача» */
.gantt__headtitle-dur { flex: none; font-size: var(--t-2xs); color: var(--text-faint); }
.ghead { position: relative; height: 52px; }
.ghead__row { position: absolute; left: 0; right: 0; }
.ghead__row--major { top: 0; height: 26px; }
.ghead__row--minor { top: 26px; height: 26px; }
.ghead__cell {
  position: absolute; top: 0; height: 26px; display: flex; align-items: center;
  font-size: var(--t-sm); font-weight: 600; color: var(--text);
  border-left: 1px solid var(--border); overflow: hidden; white-space: nowrap;
}
.ghead__label { padding-left: var(--s-2); will-change: transform; }
.ghead__day {
  position: absolute; top: 0; height: 26px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  border-left: 1px solid var(--border); font-size: var(--t-2xs); color: var(--text-dim);
  overflow: hidden;
}
.ghead__day.is-off { background: var(--off); color: var(--text-faint); }
.ghead__day.is-today { background: var(--accent); color: #fff; font-weight: 600; }
.ghead__day.is-today .ghead__dwd { color: rgba(255,255,255,.8); }
.ghead__dnum { font-size: var(--t-xs); line-height: 1.05; color: var(--text); font-weight: 500; }
.ghead__day.is-off .ghead__dnum { color: var(--text-faint); }
.ghead__day.is-today .ghead__dnum { color: #fff; }
.ghead__dwd { font-size: var(--t-2xs); line-height: 1.1; text-transform: uppercase; letter-spacing: .02em; }

.grow {
  display: flex; align-items: center; gap: var(--s-05); height: var(--row-h);
  padding-left: calc(6px + var(--depth) * 15px); padding-right: var(--s-1);
  border-bottom: 1px solid var(--border); cursor: pointer;
  -webkit-tap-highlight-color: transparent; position: relative;
  transition: background var(--fast);
}
.grow__color {
  width: 3px; height: 62%; border-radius: var(--radius-sm); flex: none; margin-right: var(--s-1);
  background: transparent;
}
.grow__color.is-set { box-shadow: 0 0 0 1px rgba(0,0,0,.06); }
.grow__more {
  width: 22px; height: 22px; flex: none; border-radius: var(--radius-sm);
  display: grid; place-items: center; color: var(--text-faint);
  font-size: var(--t-md); line-height: 1; opacity: 0; transition: opacity .1s, background .1s;
}
.grow:hover .grow__more, .grow.is-selected .grow__more { opacity: 1; }
.grow__more:hover { background: var(--border); color: var(--text); }
.grow__input {
  width: 100%; height: 24px; padding: 0 var(--s-1); font-size: var(--t-base);
  border: 1px solid var(--accent); border-radius: var(--radius-sm); background: var(--surface);
  outline: none; box-shadow: 0 0 0 3px var(--accent-soft);
}
.gantt.is-rowdrag { cursor: grabbing; user-select: none; }
.gantt.is-rowdrag .grow { cursor: grabbing; }
.grow.is-ghost { opacity: .4; }
.gantt__dropline {
  position: absolute; height: 2px; right: 6px; z-index: 6; pointer-events: none;
  background: var(--accent); border-radius: var(--radius-sm);
}
.gantt__dropline::before {
  content: ''; position: absolute; left: -4px; top: -3px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent);
}
/* Подсказка «куда попадёт»: иначе непонятно, станет задача подзадачей или нет */
.gantt__dropline::after {
  content: attr(data-hint); position: absolute; left: 10px; top: -9px;
  background: var(--accent); color: #fff; font-size: var(--t-2xs); font-weight: 500;
  padding: var(--s-05) var(--s-2); border-radius: var(--radius); white-space: nowrap;
}
.grow:hover { background: var(--surface-2); }
/* Выбранная строка помечается полосой слева: заливка одна на всю ширину
   спорит с цветными метками задач */
.grow.is-selected { background: var(--accent-soft); }
.grow.is-selected::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--accent);
}
.grow__twist {
  width: 17px; flex: none; text-align: center; color: var(--text-faint); font-size: var(--t-2xs);
}
.grow__twist.is-active { cursor: pointer; color: var(--text-dim); }
.grow__text { flex: 1; min-width: 0; }
.grow__name {
  font-size: var(--t-base); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.25;
}
.grow.is-summary .grow__name { font-weight: 600; }
/* Когда под названием нет дат, строка занята одним текстом — и длинное название
   разворачивается на две строки вместо обрыва многоточием. Высота строки при
   этом не растёт: она задана --row-h, с которой считается вся геометрия полос. */
.grow--oneline .grow__name {
  white-space: normal; line-height: 1.2;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
/* Название и кружки исполнителей в одну строку: название ужимается, кружки — нет */
.grow__nameline { display: flex; align-items: center; gap: var(--s-1); min-width: 0; }
.grow__nameline .grow__name { min-width: 0; }
/* Кружки исполнителей отодвинуты от названия: четырёх пикселей общего зазора
   не хватало, и две буквы в кружке читались продолжением слова — «Обмерный
   планАВ». Между названием и людьми нужна пауза, внутри группы — нет. */
.grow__who { display: flex; gap: var(--s-05); flex: none; margin-left: var(--s-2); }
.grow__dot {
  width: 17px; height: 17px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--t-2xs); font-weight: 600; letter-spacing: .2px;
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--border);
}
.grow__dot--rest { background: var(--surface-2); color: var(--text-dim); }
.grow__meta { font-size: var(--t-2xs); color: var(--text-faint); white-space: nowrap; }
.grow__right { display: flex; gap: var(--s-2); align-items: center; flex: none; }
.grow__dur { font-size: var(--t-2xs); color: var(--text-faint); white-space: nowrap; }
.grow__pct { font-size: var(--t-2xs); color: var(--ok); font-weight: 600; }

.gchart { display: block; }
.gchart .off { fill: var(--off); }
.gchart .off--holiday { fill: var(--off-holiday); }
.gchart .gridline { stroke: var(--border); stroke-width: 1; opacity: .55; }
.gchart .gridline--month { stroke: var(--border-strong); opacity: .9; }
.gchart .gridline--row { stroke: var(--border); opacity: .8; }
.gchart .todayline { stroke: var(--today); stroke-width: 2; stroke-dasharray: 4 3; opacity: .85; }

.bar__body { fill: var(--bar); }
.bar__done { fill: var(--bar-done); }
.bar__done--tinted { filter: brightness(.68) saturate(1.15); }
.bar.is-critical .bar__body { fill: var(--critical); }
.bar.is-critical .bar__done { filter: brightness(.75); }
.bar.is-conflict .bar__body { stroke: var(--warn); stroke-width: 2; stroke-dasharray: 3 2; }
.bar.is-selected .bar__body { stroke: var(--text); stroke-width: 2; }
.bar .summary { fill: var(--summary); }
.bar .milestone { fill: var(--milestone); stroke: var(--surface); stroke-width: 1.5; }
.bar .baseline { fill: var(--text-faint); opacity: .45; }
/* Подпись печатается поверх сетки дней, и на плотном графике читались плохо
   обе. Обводка цветом полотна отделяет буквы от линий, не добавляя плашки. */
.bar__label {
  fill: var(--text-dim); font-size: var(--t-xs); font-family: var(--font);
  pointer-events: none;
  stroke: var(--surface); stroke-width: 3px; paint-order: stroke fill;
}
/* Подпись вехи — ромб сам по себе ничего не сообщает */
.bar__label--ms { fill: var(--text); font-weight: 600; font-size: var(--t-xs); }
.bar__grip { fill: transparent; cursor: ew-resize; }
.bar__link { fill: var(--surface); stroke: var(--border-strong); stroke-width: 1.5; cursor: crosshair; opacity: 0; }
.bar:hover .bar__link { opacity: 1; }
.bar:hover .bar__body { filter: brightness(1.08); }
.gantt__right .bar { cursor: pointer; }

.link { fill: none; stroke: var(--text-faint); stroke-width: 1.4; opacity: .8; }
.link--critical { stroke: var(--critical); opacity: 1; stroke-width: 1.8; }
.arrowhead { fill: var(--text-faint); }
.arrowhead--crit { fill: var(--critical); }
.ghostlink { stroke: var(--accent); stroke-width: 2; stroke-dasharray: 4 3; }

/* Граница между колонкой задач и диаграммой — её тянут мышью или пальцем */
.gantt__resizer {
  position: absolute; top: 0; bottom: 0; width: 7px; z-index: 6;
  cursor: col-resize; touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
.gantt__resizer::before {
  content: ''; position: absolute; left: 2px; top: 0; bottom: 0; width: 3px;
  background: transparent; transition: background .12s;
}
.gantt__resizer:hover::before { background: var(--accent); }
.gantt.is-resizing { user-select: none; cursor: col-resize; }
.gantt.is-resizing .gantt__resizer::before { background: var(--accent); }
/* Маленькая «ручка» посередине — чтобы границу вообще заметили */
.gantt__resizer::after {
  content: ''; position: absolute; left: 0; top: 50%; margin-top: -14px;
  width: 7px; height: 28px; border-radius: var(--radius-sm);
  background: var(--border-strong); opacity: 0; transition: opacity .12s;
}
.gantt__resizer:hover::after, .gantt.is-resizing .gantt__resizer::after { opacity: 1; }

.gantt__tip {
  position: absolute; z-index: 20; pointer-events: none; max-width: 300px;
  background: var(--text); color: var(--bg); padding: var(--s-2) var(--s-3);
  border-radius: var(--radius-sm); font-size: var(--t-sm); line-height: 1.45;
  box-shadow: var(--shadow-lg);
}
/* В тёмной теме подсказка не инвертируется: светлая плашка резала бы глаз.
   Поднятая поверхность темы годится на все три тёмных — раньше цвет был
   вписан числом и «Графит» получал бы синеватую плашку. */
[data-scheme='dark'] .gantt__tip { background: var(--surface-3); color: var(--text); }

/* ── Панель задачи ───────────────────────────────────────────────────────── */

.sheet-backdrop {
  position: fixed; inset: 0; background: rgba(12, 15, 20, .35);
  z-index: 40; animation: fade .15s ease;
}
@keyframes fade { from { opacity: 0 } }
.sheet {
  position: fixed; z-index: 41; background: var(--surface);
  display: flex; flex-direction: column;
  right: 0; top: 0; bottom: 0; width: min(420px, 100%);
  border-left: 1px solid var(--border); box-shadow: var(--shadow-lg);
  animation: slide-in .18s ease;
}
@keyframes slide-in { from { transform: translateX(24px); opacity: .5 } }
.sheet__head {
  display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--border); flex: none;
}
.sheet__head h3 { flex: 1; min-width: 0; }
/* Имя задачи — заголовок панели, а не первое поле формы. Правится по месту:
   поле без рамки, которое становится полем при наведении и фокусе. */
.sheet__ident { flex: 1; min-width: 0; }
.sheet__kind {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint); margin-bottom: var(--s-05);
}
.sheet__ident .input {
  height: auto; padding: var(--s-05) var(--s-1); margin-left: calc(-1 * var(--s-1));
  font-size: var(--t-lg); font-weight: 600; line-height: 1.25;
  border-color: transparent; background: none;
}
.sheet__ident .input:hover:not(:disabled) { border-color: var(--border); }
.sheet__ident .input:focus { border-color: var(--accent); background: var(--surface); }

.sheet__body { flex: 1; overflow: auto; padding: var(--s-4); }
.sheet__bits { display: flex; gap: var(--s-1); flex-wrap: wrap; margin-bottom: var(--s-4); }

/* Раздел панели: подпись и содержимое. Разделяет воздух и волосяная линия
   сверху — двадцать полей подряд без групп читались как анкета. */
.sheet__group { margin-top: var(--s-5); }
.sheet__grouphead {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint); margin-bottom: var(--s-3);
  padding-top: var(--s-4); border-top: 1px solid var(--border);
}
/* Свёрнутый раздел: то, что заводят один раз, не занимает свиток каждый раз */
.fold { margin-top: var(--s-4); border-top: 1px solid var(--border); padding-top: var(--s-3); }
.fold > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint); padding: var(--s-1) 0;
  border-radius: var(--radius-sm);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::before {
  content: '▸'; font-size: var(--t-sm); line-height: 1; transition: transform var(--fast);
  color: var(--text-faint);
}
.fold[open] > summary::before { transform: rotate(90deg); }
.fold > summary:hover { color: var(--text-dim); }
.fold[open] > summary { margin-bottom: var(--s-3); }
/* Флажок с подписью в строку — их в панели несколько */
.field--check {
  display: flex; gap: var(--s-2); align-items: center;
  font-size: var(--t-sm); color: var(--text-dim);
}
.field--check input { flex: none; }
.sheet__foot {
  padding: var(--s-3) var(--s-4); border-top: 1px solid var(--border);
  display: flex; gap: var(--s-2); flex: none; align-items: center;
}
.sheet__status { font-size: var(--t-sm); color: var(--text-dim); transition: color .15s; }
.sheet__status.is-ok { color: var(--ok); }
.sheet__status.is-error { color: var(--critical); }
.sheet__grip { display: none; }

.modal-backdrop {
  position: fixed; inset: 0; background: rgba(9, 9, 11, .45);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  z-index: 50; display: grid; place-items: center; padding: var(--s-4);
  animation: fade-in .12s var(--ease);
}
.modal {
  background: var(--raised); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  border: 1px solid var(--border);
  width: min(460px, 100%); max-height: 88dvh; display: flex; flex-direction: column;
  animation: modal-in .16s var(--ease);
}
@keyframes fade-in { from { opacity: 0 } }
@keyframes modal-in { from { opacity: 0; transform: translateY(8px) scale(.98) } }
.modal__head { padding: var(--s-4) var(--s-4); border-bottom: 1px solid var(--border); display: flex; gap: var(--s-3); align-items: center; }
.modal__head h3 { flex: 1; }
.modal__body { padding: var(--s-4); overflow: auto; }
.modal__foot { padding: var(--s-4) var(--s-4); border-top: 1px solid var(--border); display: flex; gap: var(--s-2); justify-content: flex-end; }

/* Сообщение сверху: снизу на телефоне полоса разделов, а на компьютере в тот
   угол никто не смотрит. Сверху же взгляд и так бывает — там заголовок. */
.toast {
  position: fixed; left: 50%; top: calc(12px + env(safe-area-inset-top));
  transform: translateX(-50%);
  display: flex; align-items: center; gap: var(--s-3);
  background: var(--text); color: var(--bg); padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-full);
  font-size: var(--t-base); z-index: 90; box-shadow: var(--shadow-lg);
  animation: toast-in .18s var(--ease);
  max-width: calc(100% - 32px); text-align: center;
}
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, -10px) } }
.toast--error { background: var(--critical); color: #fff; }
/* «Отменить» — тише самого сообщения: она предлагает откат, а не действие.
   Тёмная кнопка на подсказке тянула бы внимание сильнее, чем сам текст. */
.toast__act {
  border: 1px solid currentColor; background: none; cursor: pointer;
  font: inherit; font-size: var(--t-xs); font-weight: 500;
  color: inherit; opacity: .6; padding: 1px var(--s-2);
  border-radius: var(--radius-sm); margin-left: var(--s-2);
}
.toast__act:hover { opacity: 1; }

/* Подсказка при наведении. Своя, а не браузерная: та всплывает через секунду
   с лишним — за это время уже нажимают наугад. */
.tip {
  position: fixed; z-index: 95; pointer-events: none;
  background: var(--text); color: var(--bg);
  padding: var(--s-1) var(--s-3); border-radius: var(--radius);
  font-size: var(--t-xs); line-height: 1.4; max-width: 260px;
  box-shadow: var(--shadow-md); animation: fade-in .1s var(--ease);
}

.linkrow, .assignrow {
  display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) 0;
  border-bottom: 1px solid var(--border); font-size: var(--t-base);
}
.linkrow:last-child, .assignrow:last-child { border-bottom: none; }
.linkrow__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.progress-wrap { display: flex; align-items: center; gap: var(--s-3); }
.progress-wrap input[type=range] { flex: 1; accent-color: var(--accent); }
.progress-wrap span { width: 42px; text-align: right; font-variant-numeric: tabular-nums; }

.legend { display: flex; gap: var(--s-4); flex-wrap: wrap; font-size: var(--t-sm); color: var(--text-dim); padding: var(--s-2) var(--s-3); }
.legend i { display: inline-block; width: 12px; height: 8px; border-radius: var(--radius-sm); margin-right: var(--s-1); vertical-align: middle; }

/* ── Мобильная раскладка ─────────────────────────────────────────────────── */

@media (max-width: 760px) {
  .topbar { height: 50px; padding: 0 var(--s-3); gap: var(--s-1); flex: none; }
  .topbar .topbar__hide { display: none; }
  .topbar__title { font-size: var(--t-base); max-width: 34vw; }
  .grow { padding-left: calc(5px + var(--depth) * 11px); }
  .grow__name { font-size: var(--t-sm); }
  .grow__meta { font-size: var(--t-2xs); }
  .ghead__day { font-size: var(--t-2xs); }
  /* Прятали имя, когда рядом оставался квадратный знак. Знака больше нет,
     и спрятать логотип значит оставить шапку без левого края и без кнопки
     «на главную» — эта область кликабельна. Поэтому не прячем, а уменьшаем. */
  .topbar__brand .brand__name { height: .8em; }
  .page { padding: var(--s-4) var(--s-3) 60px; }
  .boards { grid-template-columns: 1fr; }
  .sheet {
    left: 0; right: 0; top: auto; bottom: 0; width: 100%;
    max-height: min(86dvh, calc(100dvh - 40px));
    border-radius: var(--radius-lg) var(--radius-lg) 0 0; border-left: none;
    padding-bottom: env(safe-area-inset-bottom);
    animation: slide-up .2s ease;
  }
  @keyframes slide-up { from { transform: translateY(100%) } }
  .sheet__grip {
    display: block; width: 38px; height: 4px; border-radius: var(--radius-sm);
    background: var(--border-strong); margin: var(--s-2) auto 0; flex: none;
  }
  .sheet__body { padding: var(--s-4); }
  /* Таблица шире экрана листается вбок, и об этом надо сказать: на телефоне
     от месяца видно четырнадцать дней из тридцати одного, а край обрезан
     ровно по границе ячейки и выглядит концом таблицы, а не обрывом.

     Тень у правого края — приём с двумя слоями фона: слой `local` едет вместе
     с содержимым и закрывает тень, когда доехали до конца, слой `scroll`
     стоит на месте. Обработчик прокрутки для этого не нужен. */
  .table--wide {
    display: block; overflow-x: auto; white-space: nowrap;
    background:
      linear-gradient(to left, var(--surface), transparent) right center / 20px 100% no-repeat local,
      radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .13), transparent)
        right center / 10px 100% no-repeat scroll;
  }
  .board__bar {
    padding: var(--s-2) var(--s-3); flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .board__bar::-webkit-scrollbar { display: none; }
  .board__bar > * { flex: none; }
  .board__bar .spacer { display: none; }
  .btn { height: 38px; }
  .seg button { height: 36px; }
}

@media (pointer: coarse) {
  .bar__grip, .bar__link { display: none; }
  /* На телефоне границу надо ещё суметь поймать пальцем */
  .gantt__resizer { width: 20px; margin-left: -7px; }
  .gantt__resizer::after {
    opacity: 1; left: 5px; width: 9px; height: 34px; margin-top: -17px;
    background: var(--border-strong);
    box-shadow: 0 0 0 2px var(--surface);
  }
  .grow__more { opacity: 1; width: 26px; height: 26px; }
  .grow { padding-right: var(--s-2); }
}

@media print {
  .topbar, .board__bar, .sheet, .modal-backdrop { display: none !important; }
  .gantt__scroll { position: static; overflow: visible; }
}

/* ── Палитра цветов ──────────────────────────────────────────────────────── */
/* Ряд кружков цвета. Имя .palette занято окном поиска по ⌘K - если назвать
   так же, правила окна перебьют этот ряд и кружки встанут столбиком. */
.swatchrow { display: flex; gap: var(--s-2); flex-wrap: wrap; }
.swatch {
  width: 28px; height: 28px; border-radius: 50%; cursor: pointer; flex: none;
  border: 2px solid transparent; box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
  transition: transform .08s; padding: 0;
}
.swatch:hover { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--border-strong); }
.swatch.is-active { border-color: var(--text); }
.swatch--none { background: var(--surface-2); position: relative; }
.swatch--none::after {
  content: ''; position: absolute; inset: 6px 3px; border-top: 2px solid var(--critical);
  transform: rotate(-45deg); transform-origin: center;
}

/* ── Канбан ──────────────────────────────────────────────────────────────── */

.kanban {
  display: flex; gap: var(--s-3); align-items: flex-start;
  overflow-x: auto; overflow-y: hidden; padding: var(--s-05) var(--s-05) var(--s-3);
  -webkit-overflow-scrolling: touch;
}
/* Что доска листается вбок, ниоткуда не следовало: последняя колонка просто
   обрывалась краем. Тень у края появляется, только когда есть куда листать. */
.kanwrap { position: relative; }
.kanwrap::after {
  content: ''; position: absolute; top: 0; bottom: var(--s-3); right: 0; width: 40px;
  pointer-events: none; opacity: 0; transition: opacity var(--fast);
  background: linear-gradient(to right, transparent, var(--bg));
}
.kanwrap.has-more::after { opacity: 1; }
/* Колонка не сжимается ниже читаемой ширины: при группировке по исполнителю их
   бывает полтора десятка, и раньше они превращались в полоски. Что не влезло —
   листается вбок полосой самого канбана. Своей вертикальной прокрутки у колонки
   нет: вертикаль всегда одна, общая прокрутка содержимого. */
.kcol {
  /* Одинаковая ширина у всех: пустая «Готово» растягивалась наравне с полными,
     а при семи этапах комплектации колонки расходились по ширине содержимого.
     Но ширина эта не постоянная: пока колонки помещаются, они делят полосу
     поровну и занимают её целиком. Четыре колонки по 272 пикселя на большом
     мониторе стояли в левой половине, а справа оставалось пустое поле. */
  flex: 1 1 272px; min-width: 272px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius); display: flex; flex-direction: column;
}
.kcol.is-over { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.kcol__head {
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-3) var(--s-3); border-bottom: 1px solid var(--border);
}
.kcol__dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
/* Лицо в заголовке колонки — когда колонка это человек. Кружок цвета
   там не значит ничего: цвет у всех людей одинаковый, а различать их
   надо с одного взгляда. */
.kcol__face { flex: none; }
.kcol__name { font-size: var(--t-base); font-weight: 600; flex: 1; min-width: 0; }
.kcol__name--two { line-height: 1.2; }
/* Подпись под именем: в «Планировании» это загрузка человека. Тише имени
   и мельче — это уточнение к нему, а не вторая строка заголовка. */
.kcol__note {
  font-size: var(--t-2xs); font-weight: 500; color: var(--text-faint);
  margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.kcol__note.is-over { color: var(--critical); }
.kcol__count {
  font-size: var(--t-xs); color: var(--text-dim); background: var(--surface);
  border-radius: var(--radius-full); padding: var(--s-05) var(--s-2); border: 1px solid var(--border);
}
/* Пустая колонка тоже должна быть крупной мишенью для перетаскивания */
.kcol__body { padding: var(--s-2); display: flex; flex-direction: column; gap: var(--s-2);
  min-height: 180px; }
.kcol__empty { font-size: var(--t-sm); color: var(--text-faint); text-align: center; padding: var(--s-4) 0; }

/* Карточка задачи. Главное на ней — название, поэтому оно набрано весом, а всё
   остальное приглушено. До этого было наоборот: название шло обычным
   начертанием, а вокруг него горели цветные плашки метки, приоритета, звёзд и
   процента — и глазу не за что было зацепиться. */
/* Рамка ровная со всех сторон.
   
   Слева стояла жирная полоса цвета задачи — наследство диаграммы Ганта, где
   цветом различают полосы. На карточках она делала список пёстрым: у каждой
   строки свой цвет, и по нему ничего не читалось, потому что задавать этот
   цвет в интерфейсе уже негде — палитра из карточки задачи убрана, а данные
   с ней остались от старых задач и от песочницы. Знак, который нельзя ни
   поставить, ни объяснить, — это не знак, а рябь.
   
   Цвет остаётся там, где он что-то значит: на полосах диаграммы. */
.kcard {
  background: var(--raised); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--s-3); cursor: pointer;
  box-shadow: var(--shadow-xs); transition: transform .1s, box-shadow .1s, border-color var(--fast);
}
.kcard:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--border-strong);
}
.kcard.is-dragging { opacity: .35; }
/* Копия карточки, которая едет за курсором или пальцем */
.kcard--ghost {
  position: fixed; z-index: 60; pointer-events: none; margin: 0;
  box-shadow: var(--shadow-lg); transform: rotate(1.5deg);
  opacity: .95;
}
/* Палец по карточке тянет её вбок, а вверх-вниз по-прежнему листает колонку */
.kcard { touch-action: pan-y; }
.kcard__stage { font-size: var(--t-2xs); color: var(--text-faint); margin-bottom: var(--s-1); }
/* Название и кнопка хода — в одну строку, кнопка не сжимается.
   Кружок выше строки текста, поэтому название опускается на полразницы:
   в одну строку оно встаёт ровно по центру кружка, в несколько — кружок
   приходится на середину первой строки, а не на середину всего абзаца.
   Держать их по верхнему краю было нельзя: кружок крупнее, и текст рядом
   с ним всплывал. */
.kcard__title { display: flex; align-items: flex-start; gap: var(--s-2); }
.kcard__title .kcard__name {
  flex: 1; min-width: 0;
  /* Полразницы между кружком хода и строкой названия: так они совмещаются
     серединами. Кегль здесь тот же, что у `.kcard__name` ниже, — разъедутся
     они, и название снова всплывёт над кружком. */
  margin-top: calc((var(--step-size) - var(--t-base) * 1.35) / 2);
}

/* Кнопка хода задачи: ведёт её по колонкам доски одним нажатием */
.stepbtn {
  flex: none; width: var(--step-size); height: var(--step-size); padding: 0;
  border: none; background: none; cursor: pointer; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--st-color, var(--text-faint));
  transition: color var(--fast), transform var(--fast);
}
/* Значок тянется за кнопкой: размер задан здесь, а не в разметке, — иначе
   при правке --step-size кружок остался бы прежним внутри большей мишени */
.stepbtn > svg { width: 100%; height: 100%; display: block; }
.stepbtn:hover { color: var(--accent); }
.stepbtn:active { transform: scale(.94); }
.stepbtn:disabled { opacity: .5; cursor: default; }
.stepbtn.is-start { color: var(--text-faint); }
.stepbtn.is-start:hover { color: var(--accent); }
.stepbtn.is-circle:hover, .stepbtn.is-check:hover { color: var(--ok); }
.stepbtn.is-done { color: var(--ok); }

/* Пустая карточка «новая задача»: та же форма, но пунктиром */
.kcard--add {
  display: flex; align-items: center; justify-content: center; gap: var(--s-2);
  background: none; border: 1px dashed var(--border-strong); border-left-width: 1px;
  border-radius: var(--radius); min-height: 96px; cursor: pointer;
  color: var(--text-faint); font-family: inherit; font-size: var(--t-base); font-weight: 500;
  box-shadow: none; transition: border-color var(--fast), color var(--fast), background var(--fast);
}
.kcard--add:hover {
  border-color: var(--accent); color: var(--accent);
  background: var(--accent-soft); transform: none;
}
.kcard--add__plus { font-size: var(--t-lg); line-height: 1; }

/* Настройки списка «Моя работа»: порядок и группы */
.seg--home button { height: 26px; font-size: var(--t-sm); padding: 0 var(--s-2); }
.hblock__head .seg--home { flex: none; }
/* Подпись группы: тише заголовка раздела, но заметнее строки задачи */
.hgroup {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint);
  margin: var(--s-5) 0 var(--s-3);
}
.hgroup:first-child { margin-top: 0; }
.hgroup__n {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: 0; color: var(--text-faint); opacity: .8;
}
/* «Показать все» — под списком */
.hblock__all {
  margin-top: var(--s-4); border: none; background: none; cursor: pointer;
  font-family: inherit; font-size: var(--t-sm); font-weight: 500; color: var(--text-dim);
  padding: var(--s-2) 0;
}
.hblock__all:hover { color: var(--accent); }

/* Перетаскивание карточки: своя рука, приподнятая копия под курсором и
   подсветка того места, куда карточка встанет */
.kcard--free.is-draggable { cursor: grab; }
.kcard--free.is-draggable:active { cursor: grabbing; }
.kcard--free.is-dragging { opacity: .3; }
/* Место приземления — черта между карточками, а не подсветка соседа:
   карточку кладут МЕЖДУ задачами, а не «в» задачу */
.cardsgrid--home.is-reordering { position: relative; }
.kcard--free.is-insert-before,
.kcard--free.is-insert-after { position: relative; }
.kcard--free.is-insert-before::before,
.kcard--free.is-insert-after::after {
  content: ''; position: absolute; top: -2px; bottom: -2px; width: 3px;
  border-radius: var(--radius-full); background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.kcard--free.is-insert-before::before { left: calc(-1 * var(--s-2) / 2 - 1px); }
.kcard--free.is-insert-after::after { right: calc(-1 * var(--s-2) / 2 - 1px); }
/* Название в карточке — до трёх строк: длинное имя не должно ни ломать сетку,
   ни обрываться на первом слове */
/* Название задачи — 15-м и весом 500. Найдено с трёх заходов.

   Сначала оно шло вровень с подписями вокруг и терялось среди них. Тогда
   его подняли до 17 и 600 — и оно стало крупнее всего на экране: в колонке
   помещалось четыре карточки вместо пяти, а в списке строка задачи весила
   больше заголовка раздела над ней. Опустили до 14 и 400, как обычный
   текст, — стало тихо: название перестало собирать взгляд первым, хотя
   ради него карточку и открывают.

   Середина: кегль основного текста (15) и вес органов управления (500).
   Ни одной новой ступени сверх тех, что в приложении уже есть, — а от
   подписей вокруг, набранных 12-м и тише цветом, отличается и тем, и
   другим сразу. */
.kcard__name {
  font-size: var(--t-base); line-height: 1.35; font-weight: 500;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
/* Огонёк просрочки и подсветка карточки: видно, не читая */
/* Огонёк приоритета — в бледном кружке.
   
   Голый значок в строке терялся: он единственный говорит «раньше остального»,
   а весил столько же, сколько любая из меток рядом. Кружок даёт ему поле —
   знак перестаёт спорить с соседями за место и ловится боковым зрением.
   
   Диаметр ровно как у кружка хода (--step-size): строку названия держит
   именно он, и огонёк вписывается в готовую высоту, ничего не раздвигая.
   Слева круг действия, справа круг важности, одного размера — строка
   получает две опоры вместо одной опоры и одного пятна.
   
   Заливка считается от цвета ступени приоритета и полотна карточки, а не
   написана числом: у шкалы может быть сколько угодно ступеней своих цветов,
   и каждая должна лечь на любую из шестнадцати тем. Доля та же, что у
   стикеров, — ровно столько краски, чтобы кружок читался подложкой, а не
   меткой. */
.kcard__hot {
  flex: none; width: var(--step-size); height: var(--step-size);
  border-radius: 50%; display: grid; place-items: center;
  font-size: var(--t-base); line-height: 1;
  background: color-mix(in srgb, var(--prio-color, var(--critical)) 16%, var(--raised));
}
/* Две записи одного срока: полная и короткая. Короткая живёт только
   в строках на телефоне, всё остальное время её нет. */
.kcard__date-brief { display: none; }
/* Просрочка — рамкой, а не заливкой.
   Заливка была верной, пока просроченных было одна-две: красная строка среди
   белых сразу видна. Но их бывает шесть из девяти — и тогда красным залит
   весь список, читается он как экран аварии, а невыделенной оказывается
   как раз работа, которая идёт по плану. Рамка говорит то же самое и не
   перекрашивает страницу: красное остаётся у даты и у огонька. */
.kcard.is-hot { border-color: var(--critical); }
.kcard.is-hot:hover { border-color: var(--critical); box-shadow: var(--shadow-md); }
/* Подвал карточки — по правому краю: срок, счёт по чек-листу, статус, лица.
   
   То же самое и в том же месте, что в строке списка на главной, где эта
   группа стоит справа от меток. Одно приложение — одно правило: слева то,
   что про саму задачу (название и метки), справа то, что про её ход (когда
   и у кого). Пока подвал карточки жался влево, одна и та же задача
   в колонке и в строке читалась по-разному, и взгляд каждый раз заново искал,
   где смотреть срок. */
.kcard__meta {
  display: flex; align-items: center; gap: var(--s-2); margin-top: var(--s-3);
  flex-wrap: wrap; justify-content: flex-end;
}
/* Огонёк приоритета стоит в подвале первым и распоркой отжимает от себя
   всё остальное: подвал прижат вправо, а важность — в левый угол. У названия
   он раньше забирал ширину в каждой из трёх строк, а на карточке это
   несколько слов, которых не видно. */
.kcard__meta > .kcard__hot:first-child,
.kcard__meta > .kcard__hotbtn:first-child { margin-right: auto; }
/* Завести задачу прямо в колонке. В покое почти не видно — проявляется,
   когда мышь над колонкой: постоянная кнопка в каждой колонке превратила бы
   доску в панель кнопок. */
/* Видна всегда, только приглушена: она проявлялась при наведении на колонку,
   а на сенсорном экране наведения нет — и завести задачу в колонке было
   нельзя вовсе */
.kcol__add {
  border: 1px dashed var(--border); border-radius: var(--radius);
  padding: var(--s-2) var(--s-3); font-size: var(--t-sm); color: var(--text-faint);
  cursor: pointer; text-align: center; transition: color var(--fast), border-color var(--fast);
  margin-top: var(--s-1); opacity: .7;
}
.kcol:hover .kcol__add { opacity: 1; }
.kcol .kcol__add:hover { color: var(--accent); border-color: var(--accent); opacity: 1; }

/* Цифры одной ширины везде, где их читают колонкой: иначе даты «пляшут»
   и список перестаёт читаться сверху вниз */
.kcard__date { font-size: var(--t-2xs); color: var(--text-dim); font-variant-numeric: tabular-nums; }
/* Просрочка — цветом, и только им.
   Красным, полужирным и моноширинным разом срок кричал громче названия
   задачи, хотя сам по себе он новость только тем, что просрочен. Про то же
   говорит и красная обводка карточки — три знака об одном. */
.kcard__date.is-late { color: var(--critical); }
/* Срок — кнопка, а не подпись: нажатие открывает календарь прямо на карточке.
   В покое выглядит текстом, чтобы не превращать карточку в панель кнопок. */
.kcard__date-btn {
  font: inherit; color: inherit; background: none; border: none; cursor: pointer;
  padding: 1px var(--s-1); margin: -1px calc(var(--s-1) * -1);
  border-radius: var(--radius-sm); transition: background var(--fast), color var(--fast);
}
.kcard__date-btn:hover { background: var(--surface-2); color: var(--text); }
.kcard__date.is-late .kcard__date-btn:hover { color: var(--critical); }
.kcard__date-input {
  font: inherit; font-family: var(--mono); color: var(--text);
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  padding: 0 var(--s-1); background: var(--surface); max-width: 140px;
}
.kcard__pct { font-size: var(--t-2xs); color: var(--ok); font-weight: 600; }
.kcard__flag {
  font-size: var(--t-2xs); color: var(--warn); border: 1px solid var(--warn);
  border-radius: var(--radius-full); padding: 0 var(--s-2);
}
.kcard__who { display: flex; gap: var(--s-05); margin-left: auto; }
.kcard__dot {
  /* Кружки были 19 пикселей: две буквы в них читались с трудом, а именно
     по ним на доске и узнают, чья это задача */
  width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--t-2xs); font-weight: 600;
  background: var(--accent-soft); color: var(--accent); border: 1px solid var(--border);
}
.kcard__dot--rest { background: var(--surface-2); color: var(--text-dim); }
/* Статус прямо на карточке: та же плашка, но нажимается. Цветная точка слева
   берётся из цвета колонки — так статус узнаётся до чтения слова. */
.kcard__status {
  display: inline-flex; align-items: center; gap: var(--s-2);
  height: 24px; padding: 0 var(--s-2) 0 var(--s-3); border-radius: var(--radius-full);
  background: var(--surface-2); color: var(--text-dim); border: 1px solid transparent;
  font-size: var(--t-2xs); font-weight: 600; font-family: inherit; cursor: pointer;
  transition: border-color var(--fast), color var(--fast);
}
.kcard__status::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--st-color, var(--text-faint));
}
.kcard__status:hover, .kcard__status.is-open {
  border-color: var(--border-strong); color: var(--text);
}

@media (max-width: 760px) {
  .kcol { flex-basis: 78vw; max-width: 78vw; max-height: none; }
}

/* ── Чек-лист и обсуждение в карточке задачи ─────────────────────────────── */

.ckbar { height: 5px; border-radius: var(--radius-sm); background: var(--surface-2); overflow: hidden; margin-bottom: var(--s-3); }
.ckbar__fill { height: 100%; background: var(--ok); transition: width .2s; }
.ckrow {
  display: flex; align-items: center; gap: var(--s-3); padding: var(--s-2) 0;
  border-bottom: 1px solid var(--border); font-size: var(--t-base);
}
.ckrow:last-of-type { border-bottom: none; }
.ckrow.is-done span { color: var(--text-faint); text-decoration: line-through; }

/* Флажок чек-листа — свой, а не системный.
   
   Системный флажок рисует операционная система: на Windows это синий
   квадратик с чужим скруглением и чужим синим, на маке — свой, и оба
   не имеют отношения ни к одной из шестнадцати наших тем. В строке,
   где всё остальное собрано из наших токенов, он читался деталью
   от другого приложения.
   
   Форма — кружок, как у кнопки хода задачи: чек-лист и есть та же работа,
   только помельче, и отмечают её тем же движением. Галочка внутри нарисована
   двумя линиями через clip-path — так она масштабируется и красится цветом
   темы, в отличие от шрифтовой «✓», которая в каждом шрифте своя. */
.ckrow input[type='checkbox'] {
  appearance: none; -webkit-appearance: none; margin: 0; flex: none;
  width: 20px; height: 20px; border-radius: 50%;
  border: 1.5px solid var(--border-strong); background: var(--raised);
  cursor: pointer; position: relative;
  transition: background var(--fast), border-color var(--fast);
}
.ckrow input[type='checkbox']:hover:not(:disabled) { border-color: var(--ok); }
.ckrow input[type='checkbox']:checked {
  background: var(--ok); border-color: var(--ok);
}
.ckrow input[type='checkbox']:checked::after {
  content: ''; position: absolute; left: 4.5px; top: 5.5px;
  width: 9px; height: 5px;
  border-left: 2px solid var(--raised); border-bottom: 2px solid var(--raised);
  transform: rotate(-45deg);
}
.ckrow input[type='checkbox']:disabled { cursor: default; opacity: .55; }

.cmt { display: flex; gap: var(--s-2); padding: var(--s-2) 0; border-bottom: 1px solid var(--border); }
/* Поле ответа стоит на той же линии, что и чужие реплики: видно, что пишешь
   в ту же ленту, а не заполняешь форму под ней */
.cmt--new { border-bottom: none; padding-top: var(--s-3); align-items: flex-start; }
.cmt__input { min-height: 38px; resize: none; overflow: hidden; }
.cmt__foot { display: flex; align-items: center; gap: var(--s-3); margin-top: var(--s-2); }
.cmt:last-of-type { border-bottom: none; }
.cmt__head { display: flex; gap: var(--s-2); align-items: baseline; font-size: var(--t-sm); }
.cmt__body { font-size: var(--t-base); white-space: pre-wrap; word-break: break-word; margin-top: var(--s-05); }

/* ── Ресурсы и показатели ────────────────────────────────────────────────── */

/* Загрузка людей. Полоса тянулась во всю ширину карточки, поэтому 57% и 43%
   выглядели почти одинаково и сравнивать приходилось цифры. Теперь у полосы
   фиксированная шкала с отметкой полной занятости — разницу видно сразу. */
.loadbar {
  position: relative; height: 8px; border-radius: var(--radius-full);
  background: var(--surface-2); margin-top: var(--s-3);
  width: min(420px, 100%);
}
.loadbar__fill {
  height: 100%; background: var(--accent); transition: width .25s;
  border-radius: var(--radius-full);
}
.loadbar__fill.is-over { background: var(--critical); }
/* Отметка «сто процентов»: без неё непонятно, много 57 или мало */
.loadbar::after {
  content: ''; position: absolute; right: 0; top: -3px; bottom: -3px; width: 1px;
  background: var(--border-strong);
}
.loadbar__cap {
  font-size: var(--t-2xs); color: var(--text-faint); margin-top: var(--s-1);
  display: block; width: min(420px, 100%); text-align: right;
}

/* Строка человека в загрузке: лицо, имя с процентом, полоса, подпись под ней.
   Лицо, а не инициалы: в списке из пятнадцати человек имя читают вторым, а
   узнают по лицу — так же, как в строке задачи и на карточке проекта. */
.loadrow { display: flex; align-items: flex-start; gap: var(--s-3); padding: var(--s-2) 0; }
.loadrow + .loadrow { border-top: 1px solid var(--border); }
/* Полоса шириной со шкалу, а не во всю карточку, — и всё, что стоит после
   неё, идёт сразу за её концом. Растянутая на всю ширину строка уносила
   счётчик просрочки к правому краю экрана, за полметра от полосы, к которой
   он относится. */
.loadrow__body { flex: 0 1 min(420px, 100%); min-width: 0; }
.loadrow__name {
  display: flex; align-items: baseline; gap: var(--s-2);
  font-size: var(--t-base); width: min(420px, 100%);
}
/* Процент у правого края полосы, а не сразу за именем: так он стоит колонкой
   и числа сравниваются между собой, а не ищутся каждое за своей фамилией. */
.loadrow__pct { margin-left: auto; font-size: var(--t-sm); color: var(--text-dim); }
/* Просрочка числом справа от строки. Здесь она именно числом, а не огоньком:
   огонёк отвечает «горит или нет», а тут вопрос «сколько», и на него нужен
   ответ, который можно сравнить с соседним. */
.loadrow__late {
  flex: none; align-self: center; min-width: 26px; text-align: center;
  background: var(--critical-soft); color: var(--critical);
  border-radius: var(--radius-full); padding: var(--s-05) var(--s-2);
  font-size: var(--t-sm); font-weight: 600;
}
/* Свёрток загрузки под колонками. Закрытый — одна строка: имя и период,
   чтобы было видно, за какой срок посчитано, не раскрывая. */
.planload { margin-top: var(--s-5); }
.planload__sum {
  display: flex; align-items: baseline; gap: var(--s-3); cursor: pointer;
  padding: var(--s-3) var(--s-4); list-style: none;
}
.planload__sum::-webkit-details-marker { display: none; }
/* Стрелка своя и слева: родной треугольник в разных браузерах разный,
   а свёртки в приложении должны выглядеть одинаково. */
.planload__sum::before {
  content: '▸'; color: var(--text-faint); font-size: var(--t-sm);
  transition: transform var(--fast); display: inline-block;
}
.planload[open] .planload__sum::before { transform: rotate(90deg); }
.planload__title { font-size: var(--t-md); font-weight: 600; }
.planload__note { font-size: var(--t-sm); }
.planload .card__body { padding-top: 0; }

/* Вкладки внутри шапки страницы: рядом с заголовком, а не полосой под ним.
   Полоса под заголовком отделяла бы шапку от содержимого лишней чертой —
   здесь под шапкой сразу колонки, и вторая горизонталь только шумит. */
.tabs--inline {
  border-bottom: none; margin-bottom: 0; margin-left: var(--s-3); overflow: visible;
}

/* Полоса проекта в разрезе «по проектам»: название, счёт задач и под ними
   тот же канбан. Полосой, а не карточкой: карточка отделяет проект от его
   работы рамкой, а здесь работа и есть содержимое полосы. */
.planband { margin-top: var(--s-5); }
.planband__head {
  display: flex; align-items: baseline; gap: var(--s-2); margin-bottom: var(--s-2);
}
.planband__name {
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--t-md); font-weight: 600; color: var(--text);
}
.planband__name:hover { color: var(--accent); }
.planband__count { font-size: var(--t-sm); color: var(--text-faint); }

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: var(--s-3); }
.tile {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--s-4) var(--s-4); box-shadow: var(--shadow);
}
.tile__num { font-size: var(--t-2xl); font-weight: 600; line-height: 1.1; letter-spacing: -.02em; }

/* ── Файлы, история, уведомления ─────────────────────────────────────────── */

.filerow {
  display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) 0;
  border-bottom: 1px solid var(--border); font-size: var(--t-base);
}
.filerow:last-of-type { border-bottom: none; }
.filerow__icon {
  flex: none; font-size: var(--t-2xs); font-weight: 700; letter-spacing: .3px;
  background: var(--accent-soft); color: var(--accent);
  border-radius: var(--radius-sm); padding: var(--s-1) var(--s-1); min-width: 38px; text-align: center;
}
.filerow__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.histrow {
  display: flex; gap: var(--s-2); padding: var(--s-1) 0; font-size: var(--t-sm);
  border-bottom: 1px solid var(--border);
}
.histrow:last-of-type { border-bottom: none; }

.notif {
  display: flex; gap: var(--s-3); padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
}
.notif:last-of-type { border-bottom: none; }
.notif.is-new { border-left: 3px solid var(--accent); padding-left: var(--s-2); margin-left: -12px; }

/* ── Редактор колонок ────────────────────────────────────────────────────── */

.modal--wide { max-width: 560px; }
.colrow { display: flex; gap: var(--s-2); align-items: center; margin-bottom: var(--s-2); }
.colrow .input { flex: 1; min-width: 0; }
.coldot {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--border-strong);
  cursor: pointer; padding: 0;
}
.select--sm { height: 30px; font-size: var(--t-base); width: auto; min-width: 165px; padding: 0 var(--s-5) 0 var(--s-2);
  background-position: calc(100% - 13px) 13px, calc(100% - 8px) 13px; }

/* ── Меню для узкого экрана ──────────────────────────────────────────────── */

.topbar__menu { display: none; }
.menurow {
  display: flex; align-items: center; gap: var(--s-3); width: 100%;
  padding: var(--s-3) var(--s-1); border: none; background: none; cursor: pointer;
  border-bottom: 1px solid var(--border); font-size: var(--t-md);
}
.menurow:last-of-type { border-bottom: none; }
/* Список выбора в окне: те же строки, что в меню, но с прокруткой — людей
   в бюро может быть и тридцать, а окно выше экрана не растёт */
.choicelist { margin-top: var(--s-2); max-height: 46vh; overflow-y: auto; }
/* Поле «Кому» в окне новой задачи: кнопка во всю ширину, как было у списка,
   но с лицом внутри */
.ktobtn { width: 100%; justify-content: flex-start; height: 38px; }
.ktobtn__in { display: flex; align-items: center; gap: var(--s-2); width: 100%; }
.ktobtn__in .caret { margin-left: auto; }
.choicelist .menurow:hover { background: var(--surface-2); }
.menurow:active { background: var(--surface-2); }

/* Меню у кнопки. Внутри те же .menurow, что и в модалках, но без разделителей
   на каждой строке: в коротком списке хайрлайн под каждым пунктом — шум. */
.menupop {
  position: fixed; z-index: 60; min-width: 208px; max-width: 280px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: var(--s-1); overflow: hidden;
}
/* На телефоне меню разворачивается во всю ширину — ширину ему ставит
   разметка, а предел в 280 точек мешал бы. Прижатое к правому краю, оно
   занимало две трети экрана и читалось как выехавшая сбоку панель. */
@media (max-width: 520px) {
  .menupop { max-width: none; }
}
/* Кто вошёл — подпись в шапке меню, а не пункт: нажимать на неё нечего */
.menupop__me {
  padding: var(--s-2) var(--s-3) var(--s-3);
  font-size: var(--t-base); font-weight: 500; color: var(--text);
}
.menupop .menurow {
  border-bottom: none; border-radius: var(--radius-sm);
  padding: var(--s-2) var(--s-3); font-size: var(--t-base);
}
.menupop .menurow:hover:not(:disabled) { background: var(--surface-2); }
.menupop .menurow:disabled { opacity: .45; cursor: default; }
.menupop .menurow.is-active { color: var(--accent); font-weight: 600; }
.menupop__sep { height: 1px; background: var(--border); margin: var(--s-1) 0; }
.btn.is-open { background: var(--surface-3); }

/* Разделитель групп в панели инструментов: он объясняет, что кнопки рядом
   делают разное, дешевле любой подписи */
.bar__sep { width: 1px; align-self: stretch; margin: var(--s-1) var(--s-1); background: var(--border); flex: none; }

@media (max-width: 760px) {
  .topbar__menu { display: inline-flex; }
  .topbar__user .btn { display: none; }        /* «Выйти» переехало в меню */
  .modal { max-height: calc(100dvh - 32px); }
  .modal__body { max-height: 66dvh; overflow: auto; -webkit-overflow-scrolling: touch; }
  .select--sm { min-width: 140px; }
}

/* ── ERP: проекты, отпуска, лёгкая версия ─────────────────────────────────── */

/* Полоса готовности на карточке доски в проекте */
.progressline {
  height: 4px; border-radius: var(--radius-sm); background: var(--surface-2);
  margin: var(--s-2) 0 var(--s-05); overflow: hidden;
}
.progressline__fill { height: 100%; background: var(--ok); border-radius: var(--radius-sm); }


/* Статистика отпуска */
/* Свой отпуск — то, ради чего сотрудник сюда и заходит: сколько накоплено,
   сколько потрачено, сколько осталось. Числа были набраны почти как обычный
   текст, а подписи под ними — мельче некуда, и весь блок читался служебной
   строкой над табелем. Теперь это четыре крупных числа с внятными подписями:
   их видно, не наклоняясь к экрану. */
.vstats { display: flex; gap: var(--s-6); flex-wrap: wrap; }
.vstat__value {
  font-size: var(--t-2xl); font-weight: 600; letter-spacing: -.3px;
  font-variant-numeric: tabular-nums; line-height: 1.15;
}
.vstat__value.is-neg { color: var(--critical); }
.vstat__value.is-pos { color: var(--ok); }
.vstat__label { font-size: var(--t-base); color: var(--text-dim); margin-top: var(--s-1); }

/* Табель отпусков: сетка год × сотрудники */
.vgrid { border-collapse: collapse; font-size: var(--t-xs); width: 100%; }
.vgrid th, .vgrid td { border: 1px solid var(--border); text-align: center; }
.vgrid th {
  padding: var(--s-1) var(--s-05); font-weight: 500; color: var(--text-dim);
  background: var(--surface-2); min-width: 17px;
}
.vgrid th.is-off { color: var(--text-faint); background: var(--surface-3, var(--surface-2)); }
.vgrid .vgrid__name {
  text-align: left; padding: var(--s-1) var(--s-2); white-space: nowrap;
  font-weight: 500; color: var(--text); background: var(--surface);
  position: sticky; left: 0; z-index: 1; min-width: 130px;
}
.vgrid thead .vgrid__name { background: var(--surface-2); font-weight: 600; }
/* Полное имя — на широком, инициал с фамилией — на телефоне. Переключает
   ширина, а не устройство: столбец мешает ровно тогда, когда он не влезает. */
.vgrid__short { display: none; }
@media (max-width: 760px) {
  .vgrid .vgrid__name { min-width: 96px; padding-left: var(--s-1); }
  .vgrid__full { display: none; }
  .vgrid__short { display: inline; }
}
td.vcell { height: 20px; min-width: 17px; padding: 0; }
.vcell--off  { background: var(--surface-2); }
.vcell--hol  { background: var(--critical-soft); }
.vcell--paid { background: var(--ok); }
.vcell--unpaid { background: #e0932a; }
.vcell--dayoff { background: #8b5cf6; }
.vcell--sick { background: #38bdf8; }
.vcell--plan {
  background: repeating-linear-gradient(45deg,
    var(--accent-soft), var(--accent-soft) 3px, transparent 3px, transparent 6px);
  outline: 1px dashed var(--accent); outline-offset: -2px;
}
.vleg { display: inline-flex; gap: var(--s-1); align-items: center; font-size: var(--t-sm); color: var(--text-dim); }
.vleg .vcell { display: inline-block; width: 14px; height: 14px; border-radius: var(--radius-sm); border: 1px solid var(--border); }
/* Сегодняшний день видно сразу: без него календарь месяца — просто сетка,
   и «кто сейчас на месте» приходится вычислять по числу в углу экрана */
.vgrid th.is-today { color: var(--accent); font-weight: 700; }
.vgrid td.vcell.is-today { box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent); }
/* Дни, выбранные под заявку */
.vgrid td.vcell.is-picked { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Буква дня недели над числом: выходные различались только оттенком,
   а в тёмной теме почти никак */
.vgrid__wd {
  display: block; font-size: 9px; line-height: 1.1; color: var(--text-faint);
  text-transform: lowercase;
}
.vgrid__d { display: block; line-height: 1.2; font-variant-numeric: tabular-nums; }
.vgrid th.is-off .vgrid__wd { color: var(--critical); }

/* Своя строка: восемь одинаковых фамилий, и свою искали глазами каждый раз.
   Двухпиксельной черты слева и жирного имени не хватало — на сетке из
   трёхсот шестидесяти клеток они терялись. Теперь подсвечена вся строка
   целиком, а не только имя: полоса тянется через весь год, и найти себя
   можно не глядя на левый край. */
.vgrid tbody tr.is-me .vgrid__name {
  font-weight: 700; color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent);
  background: var(--accent-soft);
}
.vgrid tbody tr.is-me td.vcell {
  border-top-color: var(--accent); border-bottom-color: var(--accent);
}
/* Подсветка ложится под цветные клетки отпусков, а не поверх: отпуск должен
   оставаться того же цвета, что у соседей, иначе по табелю не сравнить */
.vgrid tbody tr.is-me td.vcell:not([class*='vcell--']) { background: var(--accent-soft); }

/* День, в который отсутствует не один человек: пересечения видно сразу,
   а не при построчном сравнении */
.vgrid td.vcell.is-crowd { box-shadow: inset 0 0 0 1px var(--text); }

/* Год целиком. Смысл вида — увидеть все двенадцать месяцев разом, поэтому
   таблица раскладывается по ширине окна, а не по ширине содержимого:
   на день приходится пара пикселей, зато отпуск в две недели — заметная
   полоска, и пересечения читаются с одного взгляда. */
.vgrid--year { table-layout: fixed; width: 100%; }
.vgrid--year th, .vgrid--year td.vcell { min-width: 0; }
.vgrid--year .vgrid__name { width: 120px; min-width: 120px; }
.vgrid--year td.vcell { border-left: none; border-right: none; }
.vgrid__mo {
  display: block; font-size: 9px; color: var(--text-dim); text-align: left;
  white-space: nowrap; position: relative; left: 1px;
}

/* Кто сегодня отсутствует — словами над сеткой */
.vaway { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.vstat__hint { font-size: var(--t-xs); color: var(--text-faint); margin-top: var(--s-05); }

/* Признак «видно не всем» — замком после названия вкладки */
.tab__priv { margin-left: var(--s-1); font-size: var(--t-2xs); opacity: .7; }

/* Строка исполнителя в настройках лёгкой версии */
.perfrow { display: flex; gap: var(--s-2); align-items: center; margin-bottom: var(--s-2); flex-wrap: wrap; }
.perfrow .input { flex: 1; min-width: 150px; }
.vstat__value.is-adv { color: var(--warn); }

/* ── Боковая навигация ───────────────────────────────────────────────────── */
/* Инструменты слева и всегда на виду: человек не ищет, куда нажать, а видит
   весь набор сразу — как в Basecamp. Топбар остаётся под поиск и профиль. */

.shell { display: flex; flex: 1; min-height: 0; }

/* Разделы строкой в шапке. Полоса внизу (.nav) — только для телефона, на
   компьютере её нет: те же пять кнопок дважды на экране не нужны. */
.topnav { display: flex; align-items: center; gap: var(--s-05); min-width: 0; }
.topnav .navitem { width: auto; padding: var(--s-2) var(--s-3); }
.topnav .navitem__icon { display: none; }
.nav { display: none; }

/* Боковая колонка — контекст, а не разделы: список проектов и тот, где мы.

   Выравнивание идёт по тексту, а не по подложке строки. Подложка — то, что
   видно при наведении, — на общей линии стоять не должна: она шире текста
   на свои поля и уводит сами названия вправо. Раньше на линии логотипа
   (24, поле шапки) стоял её край, а текст уезжал на 36; теперь на линии
   логотипа текст, а подложка торчит левее, на 12. Названиям проектов
   достались освободившиеся 12 точек ширины — в узкой колонке это строка
   переноса. */
.side {
  width: var(--side-w); flex: none; display: flex; flex-direction: column; gap: var(--s-1);
  padding: var(--s-2) var(--s-3) var(--s-4) var(--s-3); overflow-y: auto;
  transition: width var(--fast);
}
/* Складка колонки: знак в строке заголовка, у правого края — у той границы,
   которую он и двигает. Своей строкой он отодвигал «Проекты» вниз, и над
   списком висело пустое поле. */
.side__fold {
  flex: none; width: 26px; height: 26px; margin-right: calc(-1 * var(--s-2));
  display: grid; place-items: center;
  border: none; border-radius: var(--radius-sm); background: none;
  color: var(--text-faint); cursor: pointer;
  transition: background var(--fast), color var(--fast);
}
.side__fold:hover { background: var(--surface-2); color: var(--text); }
.side__fold.is-folded .ic { transform: rotate(180deg); }
/* Свёрнутая колонка — полоса со знаком, а не пустое место: колонка,
   пропавшая без следа, не сообщает, чем её вернуть.

   Ширина полосы ровно 24 точки — там, где стояли названия проектов
   (поле колонки 12 плюс поле строки 12). Край полотна встаёт на ту же
   вертикаль, что и текст в развёрнутой колонке, и логотип над ним:
   складка двигает содержимое, но не сбивает общую линию приложения. */
.side.is-folded, .side--bare {
  width: calc(var(--s-3) * 2); padding-left: 0; padding-right: 0;
}
/* Колонки нет — нет и складки: складывать нечего. Полоса остаётся,
   чтобы край полотна стоял на общей вертикали приложения. */
.side--bare { overflow: hidden; }
/* От свёрнутой колонки остаётся строка заголовка, а от неё — одна складка:
   сам заголовок в 24 точки не поместится, а знак поместится. */
.side.is-folded > *:not(.side__head) { display: none; }
.side.is-folded .side__head { padding: 0; }
.side.is-folded .side__head > *:not(.side__fold) { display: none; }
.side.is-folded .side__fold {
  width: calc(var(--s-3) * 2); height: calc(var(--s-3) * 2); margin-right: 0;
}
/* Поле здесь равно полю строки проекта: заголовок колонки и названия под ним
   набраны текстом и стоят на одной вертикали */
.side__head { display: flex; align-items: center; gap: var(--s-2); padding: 0 var(--s-3); }
.side__title {
  flex: 1; font-size: var(--t-2xs); font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-faint); padding: var(--s-2) 0;
}
.side__list { display: flex; flex-direction: column; gap: 1px; }
.siderow {
  display: flex; align-items: center; gap: var(--s-2); width: 100%;
  padding: var(--s-2) var(--s-3); border: none; border-radius: var(--radius); cursor: pointer;
  background: none; color: var(--text-dim); font: inherit; font-size: var(--t-sm);
  text-align: left; transition: background var(--fast), color var(--fast);
}
.siderow:hover { background: var(--surface-2); color: var(--text); }
.siderow.is-active {
  background: var(--surface); color: var(--text); font-weight: 600; box-shadow: var(--shadow-xs);
}
/* Разделы открытого проекта: те же строки, только с отступом и тише —
   это его внутренности, а не соседи по списку. Полоски готовности у них
   нет: раздел не «сделан на 40%». */
.siderow--sub {
  padding-left: var(--s-5); font-size: var(--t-sm); color: var(--text-faint);
  min-height: 30px;
}
/* Открытый раздел помечен чертой слева, а не той же приподнятой пилюлей,
   что и проект над ним. Пилюля означает «вот где вы» на верхнем уровне;
   когда она стояла и на проекте, и на его разделе, две одинаковые заливки
   подряд читались как два равных пункта, и было непонятно, что во что
   вложено. Черта говорит то же самое, но тише и на своём уровне. */
.siderow--sub.is-active {
  font-weight: 500; color: var(--text); background: none; box-shadow: none;
  position: relative;
}
.siderow--sub.is-active::before {
  content: ''; position: absolute; left: var(--s-3); top: 6px; bottom: 6px;
  width: 2px; border-radius: 1px; background: var(--accent);
}
.siderow--sub:hover { background: var(--surface-2); }
.siderow__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.siderow__name {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere; line-height: 1.25;
}
.siderow__prog { height: 2px; border-radius: var(--radius-full); background: var(--border); overflow: hidden; }
/* У общего проекта компании это не шкала, а просто черта: тише обычной,
   чтобы её не читали как «ничего не сделано» */
.siderow__prog.is-plain { background: var(--border); opacity: .55; }
.siderow__done { display: block; height: 100%; background: var(--ok); border-radius: inherit; }
/* Метка просрочки в списке проектов. Раньше здесь стояло число в красной
   пилюле — то есть ровно форма значка уведомлений, и читали её как «три
   непрочитанных». Огонёк значит «просрочено» на карточках задач, и спутать
   его с уведомлением нельзя. Число ушло в подсказку: важно, что горит,
   а сколько — видно, когда зайдёшь. */
/* Нить внутри проекта: этап и задача, на которой мы стоим. Волосяная линия
   слева показывает, что это ветка активной строки, а не соседний проект */
.sidetrail {
  display: flex; flex-direction: column; gap: 1px;
  margin: var(--s-1) 0 var(--s-2) calc(var(--s-3) + var(--s-1));
  border-left: 1px solid var(--border);
}
.sidetrail__row {
  border: none; background: none; cursor: pointer; text-align: left; font: inherit;
  font-size: var(--t-xs); color: var(--text-faint); border-radius: var(--radius-sm);
  padding: var(--s-1) var(--s-2);
  padding-left: calc(var(--s-3) + var(--trail-depth, 0) * 10px);
  transition: background var(--fast), color var(--fast);
}
.sidetrail__row:hover { background: var(--surface-2); color: var(--text-dim); }
.sidetrail__name {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere; line-height: 1.3;
}
/* Та, на которой стоим: курсора-руки нет — идти некуда, мы уже здесь */
.sidetrail__row.is-here { color: var(--text); font-weight: 600; cursor: default; }
.sidetrail__row.is-here:hover { background: none; color: var(--text); }
.sidetrail__row--root { color: var(--text-dim); }

/* Узкий экран: контекст уступает место содержимому */
.side__spacer { flex: 1; min-height: var(--s-5); }
.roundbtn .badge { position: absolute; top: -2px; right: -2px; }

@media (max-width: 1100px) {
  .side { display: none; }
  /* Колонки нет — выравнивать не подо что, логотип занимает своё */
  .topbar__brand { flex: none; }
}
.navitem {
  display: flex; align-items: center; gap: var(--s-3); width: 100%;
  padding: var(--s-2) var(--s-3); border: none; border-radius: var(--radius); cursor: pointer;
  background: none; color: var(--text-dim); font-size: var(--t-base); font-weight: 500;
  text-align: left; font-family: inherit;
  transition: background var(--fast), color var(--fast), box-shadow var(--fast);
}
.navitem:hover { background: var(--surface-2); color: var(--text); }
/* Выбранный раздел приподнят карточкой того же цвета, что и полотно справа:
   видно, что содержимое — продолжение этой строки, а не соседняя колонка */
.navitem.is-active {
  background: var(--surface); color: var(--text); font-weight: 600;
  box-shadow: var(--shadow-xs);
}
.navitem__icon { width: 17px; text-align: center; flex: none; font-size: var(--t-md); }
.navitem__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.navitem .badge { flex: none; }

/* Три круглые кнопки в шапке и внизу колонки: поиск, уведомления, аватарка —
   и тема, настройки, админка. Одинаковый размер и форма: это один ряд
   инструментов, а не набор разных вещей. */
.roundbtn {
  width: 36px; height: 36px; flex: none; display: grid; place-items: center;
  border: none; border-radius: 50%; background: var(--surface-2); color: var(--text-dim);
  cursor: pointer; font-size: var(--t-md); font-family: inherit; position: relative;
  transition: background var(--fast), color var(--fast);
}
.roundbtn:hover { background: var(--surface-3, var(--surface)); color: var(--text); }
.roundbtn.is-open, .roundbtn.is-active { background: var(--surface); color: var(--text); box-shadow: var(--shadow-xs); }
.roundbtn--sm { width: 32px; height: 32px; font-size: var(--t-base); }

/* Аватарка — крайняя справа и без подложки: это лицо, а не кнопка */
.profilebtn {
  padding: 0; border: none; background: none; cursor: pointer; flex: none;
  border-radius: 50%; line-height: 0;
}
.profilebtn.is-open { box-shadow: 0 0 0 2px var(--accent); }
/* Круглые кнопки — плашки, а не текст, и левым краем встают на линию плашек,
   то есть на край подложки строки, а не на линию названий */
.side__tools { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-2) var(--s-2) 0 0; }

/* Поиск в шапке: узкая кнопка, разворачивается в палитру по ⌘K */
.searchbtn {
  display: flex; align-items: center; gap: var(--s-2); height: 34px; padding: 0 var(--s-3);
  border: 1px solid var(--border); border-radius: var(--radius-full); background: var(--surface-2);
  color: var(--text-dim); font-size: var(--t-base); cursor: pointer; min-width: 210px;
  font-family: inherit; transition: background var(--fast), border-color var(--fast);
}
.searchbtn:hover { background: var(--surface); border-color: var(--border-strong); }
.searchbtn kbd {
  margin-left: auto; font-size: var(--t-2xs); font-family: inherit; color: var(--text-faint);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: var(--s-05) var(--s-2); line-height: 1;
}

/* ── Палитра поиска ──────────────────────────────────────────────────────── */

.palette {
  /* Шире: на широком экране окно занимало треть, а длинные названия при этом
     обрезались — место было, им просто не пользовались */
  width: min(720px, calc(100vw - 28px)); max-height: 70vh;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; overflow: hidden;
}
/* Отбор по виду и подсказка клавиш: стрелки работали, но узнать об этом
   было неоткуда */
.palette__kinds {
  display: flex; gap: var(--s-1); padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
.palette__kind {
  border: none; background: none; cursor: pointer; font: inherit;
  font-size: var(--t-xs); color: var(--text-dim);
  padding: var(--s-1) var(--s-2); border-radius: var(--radius-sm);
}
.palette__kind:hover { background: var(--surface-2); color: var(--text); }
.palette__kind.is-active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }

.palette__hint {
  display: flex; gap: var(--s-4); padding: var(--s-2) var(--s-4);
  border-top: 1px solid var(--border); background: var(--surface-2);
  font-size: var(--t-2xs); color: var(--text-faint); flex-wrap: wrap;
}
.palette__hint kbd {
  font-family: var(--mono); font-size: 10px; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0 4px; margin-right: 2px;
}

/* Найденное подсвечено: иначе совпадение ищут в каждой строке заново */
.palette__hit {
  background: var(--warn-soft); color: inherit; border-radius: 2px;
  padding: 0 1px; font-weight: 600;
}
/* Проект различает восемь одинаковых названий — он не подпись, а метка */
.palette__sub {
  flex: none; font-size: var(--t-xs); color: var(--text-dim);
  background: var(--surface-2); border-radius: var(--radius-sm);
  padding: 1px var(--s-2); max-width: 40%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.palette__input {
  height: 50px; border: none; border-bottom: 1px solid var(--border);
  background: none; color: var(--text); font-size: var(--t-md); padding: 0 var(--s-4);
  outline: none; font-family: inherit; width: 100%;
}
.palette__list { overflow: auto; padding: var(--s-2); }
.palette__group {
  font-size: var(--t-xs); color: var(--text-faint); text-transform: uppercase;
  letter-spacing: .05em; padding: var(--s-2) var(--s-3) var(--s-1);
}
.palette__row {
  display: flex; align-items: center; gap: var(--s-2); width: 100%;
  padding: var(--s-2) var(--s-3); border: none; border-radius: var(--radius-sm); background: none;
  color: var(--text); font-size: var(--t-base); cursor: pointer; text-align: left;
  font-family: inherit;
}
.palette__row.is-cursor { background: var(--accent-soft); color: var(--accent); }
.palette__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
/* Строка поиска: срок и статус под названием — видно, что с задачей, не открывая */
.palette__state { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.palette__state .note { font-size: var(--t-xs); }
.palette__state .note.is-done { text-decoration: line-through; opacity: .7; }
.palette__row small { color: var(--text-faint); font-size: var(--t-sm); margin-left: auto; flex: none; }
.palette__icon { width: 16px; text-align: center; flex: none; opacity: .7; }
.palette__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Главная: сегодня ────────────────────────────────────────────────────── */

/* Между «Моими задачами», «Поручил» и «Наблюдаю» было 48 точек — ступень,
   которой отбивают разделы документа, а это соседние списки одного экрана.
   Вдвое меньше: разница между ними и так видна заголовком. */
.home { display: flex; flex-direction: column; gap: var(--s-5); }
/* Колонок на главной ровно две: работа и заметки. Раньше короткие списки под
   «Моей работой» вставали ещё одной парой, и экран разрезался на три полосы. */
.hblock__head {
  /* По центру, а не по базовой линии: у кнопок со значками базовой линии нет,
     и переключатель вида съезжал вверх относительно соседей.
     Высота задана жёстко и совпадает с высотой шапки правой колонки —
     иначе «Моя работа» и «Объявления» стоят на разных базовых линиях:
     слева заголовок центрируется в полосе с кнопками, справа — в полосе
     из одного текста. */
  display: flex; align-items: center; gap: var(--s-2) var(--s-3);
  margin-bottom: var(--s-5);
  min-height: 30px; flex-wrap: wrap;
}
/* Органы управления списком — одной группой: по отдельности последняя кнопка
   срывалась на вторую строку и висела там сиротой */
.hblock__tools { display: flex; align-items: center; gap: var(--s-2); flex: none; }
/* Пара кнопок-отборов. Воздух между ними задан здесь: правило ниже даёт его
   только прямым детям заголовка, а эта пара лежит внутри группы инструментов,
   и кнопки стояли впритык, читаясь одной длинной клавишей. */
.hblock__otbor { display: inline-flex; align-items: center; gap: var(--s-2); }

/* Порядок, группировка и «…» — три отдельные кнопки, между ними нужен воздух */
.hblock__head > span { display: inline-flex; align-items: center; gap: var(--s-2); }
.hblock__head > .spacer { flex: 1 1 auto; min-width: var(--s-3); }
/* Треугольник у кнопки-списка: видно, что по ней есть выбор */
.menulabel { display: inline-flex; align-items: center; gap: var(--s-2); }
/* Треугольник был в девять точек — на экране читался как пылинка, и понять,
   что у кнопки есть выбор, было нельзя */
.caret { font-size: var(--t-2xs); line-height: 1; opacity: .7; }
/* Заголовок не сжимается: иначе на телефоне «Моя работа» ломается на две
   строки, чтобы уступить место счётчику и ссылке. Уступает как раз ссылка. */
.hblock__head h2 { flex: none; }
/* Заголовок блока — обычный заголовок, а не мелкая подпись вразрядку.
   Прописные в 11px подряд над каждым разделом читаются как оформление, а не
   как навигация: глаз их пропускает и внутри страницы теряется структура. */
.hblock__head h2 {
  font-size: var(--t-md); font-weight: 600; color: var(--text);
  letter-spacing: -.01em;
}
.hblock__head .chip { flex: none; }
/* Сколько всего задач и сколько из них горит — тихой строкой у заголовка:
   по восьми карточкам не понять, все они или это верхушка тридцати */
/* Счётчик и просрочка — одна тихая строка одного кегля. Раньше это были три
   разных размера подряд: заголовок, подпись и красная плашка мельче подписи,
   и шапка читалась как набор разнородных наклеек. */
.hblock__count {
  display: inline-flex; align-items: baseline; gap: var(--s-3); flex: none;
  font-size: var(--t-sm); color: var(--text-faint);
}
.hblock__count .is-overdue { color: var(--critical); font-weight: 600; }
/* Отметка «смотрю глазами сотрудника»: без неё непонятно, почему половина
   кнопок пропала. Нажатие возвращает свои права. */
.chip--asuser { cursor: pointer; border: none; font: inherit; font-weight: 600; }

/* Значок в кнопке: буква «плюс» смотрелась мельче самой кнопки */
.plusicon { display: inline-grid; place-items: center; }
.plusicon svg { display: block; }
.hblock__more {
  margin-left: auto; font-size: var(--t-sm); color: var(--text-dim);
  background: none; border: none; cursor: pointer; font-family: inherit;
  font-weight: 500; transition: color var(--fast);
}
.hblock__more:hover { color: var(--accent); }

.hrow {
  display: flex; align-items: center; gap: var(--s-3); width: 100%;
  padding: var(--s-3) var(--s-3); border: none; border-bottom: 1px solid var(--border);
  background: none; color: var(--text); font-size: var(--t-base); cursor: pointer;
  text-align: left; font-family: inherit;
  border-radius: var(--radius);
}
.hrow:last-child { border-bottom: none; }
.hrow:hover { background: var(--surface-2); }
.hrow:first-child { border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg); }
.hrow__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
/* Название задачи важнее пути к ней: сжимаем путь, а не имя */
.hrow__name { flex: 0 1 auto; min-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hrow__where {
  color: var(--text-faint); font-size: var(--t-sm); flex: 1 1 auto;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hrow__right { margin-left: auto; flex: none; font-size: var(--t-sm); color: var(--text-dim); }
.hrow__right.is-over { color: var(--critical); }
.hrow__acts { margin-left: auto; display: flex; gap: var(--s-2); flex: none; }

/* Строка, по которой принимают решение, не обрезается: утверждать отпуск,
   не видя ни имени целиком, ни дат, нельзя. Переносится вместо многоточия. */
.hrow--decision { flex-wrap: wrap; align-items: baseline; }
.hrow--decision .hrow__name,
.hrow--decision .hrow__where {
  overflow: visible; text-overflow: clip; white-space: normal;
}
.hrow--decision .hrow__name { flex: 0 1 auto; font-weight: 500; }
.hrow--decision .hrow__where { flex: 0 1 auto; font-variant-numeric: tabular-nums; }

/* Лента: кто что сделал */
.feed { display: flex; flex-direction: column; gap: var(--s-05); }
/* Заголовок дня: без него вчерашнее и сегодняшнее выглядели случившимися
   разом — у всех строк только время, без даты */
.feed__day {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .06em;
  font-weight: 600; color: var(--text-faint);
  margin: var(--s-3) 0 var(--s-1); padding: 0 var(--s-05);
}
.feed__day:first-child { margin-top: 0; }
.feedrow {
  display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-05);
  font-size: var(--t-base); color: var(--text-dim); cursor: pointer;
  background: none; border: none; text-align: left; width: 100%; font-family: inherit;
}
.feedrow:hover { color: var(--text); }
.feedrow__ava {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: grid; place-items: center; overflow: hidden;
  background: var(--surface-2); color: var(--text-dim);
}
.feedrow__ava img { width: 100%; height: 100%; object-fit: cover; display: block; }
.feedrow__text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.feedrow__time { flex: none; color: var(--text-faint); font-size: var(--t-sm); }

/* Полоса прогресса проекта */
.prog {
  height: 6px; border-radius: var(--radius-full); background: var(--surface-3);
  overflow: hidden; display: flex; flex: none;
}
.prog__done { background: var(--ok); flex: none; transition: width .3s var(--ease); }
.prog__over { background: var(--critical); flex: none; }
.projrow {
  display: flex; align-items: center; gap: var(--s-3); width: 100%;
  padding: var(--s-3) var(--s-3); border: none; border-bottom: 1px solid var(--border);
  background: none; color: var(--text); font-size: var(--t-base); cursor: pointer;
  text-align: left; font-family: inherit;
  border-radius: var(--radius);
}
.projrow:last-child { border-bottom: none; }
.projrow:hover { background: var(--surface-2); }
.projrow:last-child { border-bottom: none; }
.projrow__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.projrow .prog { width: 84px; }

/* Хлебные крошки на доске и в проекте */
.crumbs {
  display: flex; align-items: center; gap: var(--s-1); font-size: var(--t-sm);
  color: var(--text-faint); margin-bottom: var(--s-2); flex-wrap: wrap;
}
.crumbs button {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--text-dim); font-size: inherit; font-family: inherit;
}
.crumbs button:hover { color: var(--accent); text-decoration: underline; }
.crumbs__sep { opacity: .5; }
.crumbs__cur { color: var(--text-dim); }

/* Уведомления, сгруппированные по дню */
.notifday {
  font-size: var(--t-xs); color: var(--text-faint); text-transform: uppercase;
  letter-spacing: .05em; padding: var(--s-3) 0 var(--s-1);
}
.notifday:first-child { padding-top: 0; }

/* Узкий экран: полоса разделов внизу ужимается до значков.

   Правила писались, когда навигация была колонкой слева, и адресовались
   всякому `.navitem` — а он теперь и в шапке. Между 761 и 900 точками
   выходило так: подписи в шапке спрятаны этим блоком, значки — правилом
   `.topnav .navitem__icon`, и в полосе стояли восемь пустых квадратиков.
   Поэтому здесь всё привязано к `.nav`: это про нижнюю полосу и только
   про неё. Шапка на тесном экране складывается в гамбургер — см. `is-tight`. */
@media (max-width: 900px) {
  .nav { width: 56px; padding: var(--s-3) var(--s-2); }
  .nav .navitem { justify-content: center; padding: var(--s-2) 0; position: relative; }
  .nav .navitem__label { display: none; }
  .nav .navitem .badge { position: absolute; transform: translate(13px, -11px); }
  .searchbtn { min-width: 0; }
  .searchbtn span, .searchbtn kbd { display: none; }
}

@media (max-width: 760px) {
  /* На телефоне навигация уезжает вниз — большим пальцем удобнее */
  .shell { flex-direction: column; }
  /* На телефоне разделы — полосой внизу, под большим пальцем; строка в шапке
     на 375px не помещается и уводит бренд с поиском за край */
  /* Высота полосы объявлена числом и стоит здесь одна на всех.
     Раньше высота получалась сама — из значка, подписи и полей, — а место
     под неё на странице было забито отдельным числом. Сошлось 82 против 78,
     и две строки внизу проекта навсегда остались под полосой: долистать
     до конца можно, а достать их нельзя. Теперь оба места считаются
     от одной величины и разойтись не могут. */
  .shell { --nav-h: 82px; }
  .nav {
    display: flex; width: 100%; flex-direction: row; border-right: none;
    border-top: 1px solid var(--border); padding: var(--s-2) var(--s-1);
    background: var(--surface); flex: none;
    gap: 0; overflow-x: auto;
    height: calc(var(--nav-h) + env(safe-area-inset-bottom));
    padding-bottom: calc(6px + env(safe-area-inset-bottom));
  }
  .topnav { display: none; }
  .nav .navitem.is-active { box-shadow: none; background: var(--accent-soft); color: var(--accent); }
  .nav .navitem { flex: 1; flex-direction: column; gap: var(--s-1); padding: var(--s-2) var(--s-05); font-size: var(--t-2xs); }
  .nav .navitem__icon { font-size: var(--t-lg); }
  /* Через `.nav`, а не голым классом: блок на 900 точках прячет подписи
     тем же путём, и голое правило проиграло бы ему весом — на телефоне
     под значками осталась бы пустота. */
  .nav .navitem__label { display: block; font-size: var(--t-2xs); text-align: center; }
  .nav .navitem .badge { position: absolute; transform: translate(12px, -4px); }
  /* Пятая вкладка «Ещё» — не переход, а меню, поэтому она кнопка, а не
     navitem: содержимое приходится обернуть, чтобы значок и подпись встали
     столбиком, как у соседей. Полоса не листается вбок — пять вкладок
     делят ширину поровну и всегда на месте. */
  .nav { overflow-x: visible; }
  .navitem--more { flex: 1; padding: var(--s-2) var(--s-05); }
  .navitem--more .navitem__in {
    display: flex; flex-direction: column; align-items: center; gap: var(--s-1);
  }
  /* Заголовок и правая часть делят первую строку, разделы уходят под них */
  .page__head-right { order: 2; }

  /* На телефоне разделы уже внизу — вторые двери в них из шапки убираем:
     значок базы знаний повторял пункт «Ещё → База знаний», а меню аватарки
     повторяло всё «Ещё» целиком. Наверху остаются поиск и колокольчик —
     то, чего в полосе разделов нет. */
  .topbar .roundbtn--kb, .topbar .profilebtn { display: none; }
  /* На телефоне остров не нужен: экран и так узкий, поля съедают содержимое */
  .main { order: 1; margin: 0; border: none; border-radius: 0; }
  .topbar { padding: 0 var(--s-3); height: 56px; }
  .palette { width: calc(100vw - 20px); max-height: 74vh; }
  /* На телефоне путь к задаче не помещается — остаётся имя и срок */
  .hrow__where { display: none; }
  .hrow { padding: var(--s-3) var(--s-3); gap: var(--s-2); }
  .hrow__name { flex: 1; }
  /* Строка решения — «отпуск такого-то, ждёт бухгалтера, оформить». На
     широком экране это одна строка; на телефоне она переносилась по частям,
     и каждая вставала своей строкой: кружок отдельно, имя отдельно, плашка
     отдельно, кнопки отдельно — четыре строки и две пустые полосы между
     ними. Собираем в столбец сами: имя, под ним даты с плашкой, снизу
     кнопки. Кружок в столбце не нужен вовсе — он метил строку в списке,
     а здесь строка одна. */
  .hrow--decision {
    display: grid; grid-template-columns: 1fr; gap: var(--s-1);
    align-items: start; padding: var(--s-3);
  }
  .hrow--decision .hrow__dot { display: none; }
  .hrow--decision .hrow__where { display: inline; }
  .hrow--decision .hrow__name { font-size: var(--t-base); }
  .hrow--decision .hrow__acts { margin: var(--s-1) 0 0; justify-content: flex-start; }
  /* Даты и плашка «ждёт бухгалтера» — одной строкой под именем */
  .hrow--decision .chip { justify-self: start; }

  /* Колонка по человеку — по содержимому, а не в 180 точек.

     Высота задана ради перетаскивания: пустая колонка должна быть мишенью,
     в которую можно попасть мышью. На телефоне не перетаскивают вовсе,
     а колонки идут одна под другой — и пустая занимала треть экрана
     надписью «Пусто». Три свободных человека — и до занятого не долистать. */
  .kcol__body { min-height: 0; }
  .kcol__empty { padding: var(--s-2) 0; }
  .kcol { min-width: 0; }
  /* Слева вровень с заголовком раздела: своё поле у строки сдвигало имя
     на дюжину точек вправо, и «Отпуска на оформлении» с «Виктор Шутка»
     стояли на разных вертикалях — на узком экране это видно сразу. */
  .hrow--decision { padding-left: 0; padding-right: 0; }

  /* Между работой и заметками — 24 точки вместо 48.

     На широком экране это два столбца рядом, и зазор между ними разводит
     их по горизонтали. На телефоне столбец один, и тот же зазор становится
     пустой полосой в полсотни точек поперёк экрана — между «Оформить»
     и «Объявлениями» висела дыра, за которой не видно, что там дальше. */
  /* Вес поднят нарочно: общее правило .homewrap объявлено ниже по файлу,
     и при равной силе побеждало бы оно. */
  .page--home .homewrap { gap: var(--s-5); }
  .projrow { padding: var(--s-3) var(--s-3); }
  .projrow .prog { width: 54px; }
}

/* Участник команды проекта */
.teamchip {
  display: flex; align-items: center; gap: var(--s-2);
  border: 1px solid var(--border); border-radius: var(--radius-full);
  padding: var(--s-1) var(--s-3) var(--s-1) var(--s-1); font-size: var(--t-sm);
}
.teamchip__x {
  border: none; background: none; cursor: pointer; padding: 0 var(--s-05);
  color: var(--text-faint); font-size: var(--t-sm); line-height: 1;
}
.teamchip__x:hover { color: var(--critical); }

/* Полоса нагрузки по минутам на вкладке «Сервер» */
.spark {
  display: flex; align-items: flex-end; gap: var(--s-05);
  height: 90px; padding: var(--s-1) 0; overflow: hidden;
}
.spark__bar { flex: 1; min-width: 2px; background: var(--accent-soft); border-radius: 1px; }
.spark__bar.is-slow { background: var(--warn); }
.spark__bar.is-error { background: var(--critical); }

/* ── Переключатель вида задач: канбан / гант / список ────────────────────── */

.viewseg button {
  display: grid; place-items: center; width: 34px;
}
.viewseg__icon { display: grid; place-items: center; }
.viewseg__icon svg { display: block; }

/* ── Метки на карточках задач: проект, этаж, помещение, тип ──────────────── */

.kcard__tags { display: flex; flex-wrap: wrap; gap: var(--s-1); margin-top: var(--s-2); }
/* Пустое место вместо метки — приглашение его заполнить: пунктирная плашка
   «+ Проект» или «+ Этаж» прямо на карточке */
.ktag--put {
  border: 1px dashed var(--border-strong); background: none; color: var(--text-faint);
  cursor: pointer; font: inherit; font-size: var(--t-2xs); line-height: 1;
  padding: var(--s-1) var(--s-2); border-radius: var(--radius-full);
}
.ktag--put:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
/* Ярлык задачи — проект, этаж, помещение, тип.
   
   Один вид на всё приложение: и в списке на главной, и на карточке в доске
   проекта. Пока он задавался в каждом месте своим, одна и та же метка
   выглядела в двух списках по-разному, и глаз каждый раз заново решал,
   одно ли это и то же.

   Тринадцать пикселей — середина между двумя размерами, которые тут были.
   Нажимаемый ярлык шёл пятнадцатью (см. `.ktag--act` ниже), ненажимаемый —
   двенадцатью, и в одном списке «170 Дом в Козино» выглядел крупно,
   а этажом ниже точно такой же — мелко. Пятнадцать — это кегль названия,
   с ним ярлык спорит за внимание; двенадцать на второй строке уже не
   читаются, а вглядываться в метку никто не станет.

   На две ступени мельче названия, и это главное в нём. Ярлык отвечает
   на вопрос «какая это задача», а название — на вопрос «что делать»;
   когда они одного веса, строка читается как два заголовка подряд,
   и взгляд спотыкается о метку раньше, чем о дело. */
.ktag {
  font-size: var(--t-xs); line-height: 1.4; padding: 2px var(--s-2);
  border-radius: var(--radius-full);
  background: var(--surface-2); color: var(--text-dim); white-space: nowrap;
  max-width: 190px; overflow: hidden; text-overflow: ellipsis;
}
/* Акцент на собственной мягкой заливке не дотягивал до порога: берём
   затемнённый вариант, цвет остаётся тем же по смыслу */
.ktag--project { background: var(--accent-soft); color: var(--accent-hover); font-weight: 600; }
/* Этаж — той же заливкой, что помещение. Он стоял на --surface-3, самой
   тёмной ступени набора, и на карточке оказывался темнее всего остального:
   среди светлых меток одна тёмная читалась как выделенная, хотя этаж ничем
   не важнее помещения рядом. */
.ktag--floor { background: var(--surface-2); color: var(--text-dim); }
/* Зелёный в приложении означает «готово» — помещению он не принадлежит */
.ktag--room { background: var(--surface-2); color: var(--text-dim); }
/* Тип задачи — единственный ярлык, чей цвет задаёт человек: у каждого проекта
   свой набор типов и своя палитра. Отсюда и беда: цвет выбирают по вкусу,
   а читать его приходится тринадцатым кеглем на собственной бледной заливке.
   Измерение по всем шестнадцати темам дало 3,93 — ниже порога 4,5, при котором
   мелкий текст считается читаемым, и одинаково ниже везде.

   Цвет не подменяется: тип узнают именно по нему. Он подтягивается к тексту
   темы — в светлой темнеет, в тёмной светлеет, оставаясь тем же по тону.
   Заливка при этом чуть гуще: разводить их только цветом текста значило бы
   сильно менять оттенок, а так хватает малого сдвига с обеих сторон. */
.ktag--type {
  background: color-mix(in srgb, var(--ktag-color, #64748b) 18%, transparent);
  color: color-mix(in srgb, var(--ktag-color, #64748b) 58%, var(--text));
  font-weight: 600;
}

/* Сложность — справочная величина. Янтарным она кричала наравне с «Горит»,
   хотя ничего срочного не сообщает */

.kcard__prio {
  font-size: var(--t-2xs); line-height: 1; padding: var(--s-1) var(--s-2); border-radius: var(--radius-full); font-weight: 700;
  background: color-mix(in srgb, var(--prio-color, #e05555) 14%, transparent);
  color: var(--prio-color, #e05555);
}

/* Аватарка внутри кружка исполнителя */
/* Инициалы — рисунком во всю ширину кружка. Кегль здесь больше ничего не
   решает: буквы вписаны в систему координат SVG и уменьшаются вместе с ним.
   До этого они набирались текстом, кегль задавался в rem и про размер кружка
   не знал — на узком экране и при крупном системном шрифте две буквы вылезали
   за край заливки. */
.kcard__dot > svg, .avatar > svg, .tlist__ava > svg, .grow__dot > svg,
.feedrow__ava > svg { width: 100%; height: 100%; display: block; }
/* Кружок с фотографией: заливка и рамка убираются, снимок обрезается
   по кругу. Перечислены все места, где кружок бывает, — диаграмма в том
   числе: пока её не было в списке, фотография в ней ложилась квадратом
   поверх заливки. */
.kcard__dot.has-img, .avatar.has-img, .grow__dot.has-img {
  overflow: hidden; padding: 0;
}
.grow__dot.has-img { background: none; border-color: transparent; }
.kcard__dot img, .avatar img, .tlist__ava img, .grow__dot img {
  width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block;
}
.avatar--sm { width: 22px; height: 22px; font-size: var(--t-2xs); }
.avatar--big { width: 72px; height: 72px; font-size: var(--t-2xl); }
.avaprev { flex: none; }

/* ── Список задач таблицей ───────────────────────────────────────────────── */

.tlist { overflow-x: auto; }
.tlist__table { width: 100%; border-collapse: collapse; font-size: var(--t-base); }
.tlist__table th {
  text-align: left; font-size: var(--t-xs); font-weight: 600; color: var(--text-faint);
  text-transform: uppercase; letter-spacing: .04em;
  padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--border-strong);
  white-space: nowrap; position: sticky; top: 0; background: var(--bg); z-index: 1;
}
.tlist__table td {
  padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--border); vertical-align: middle;
  white-space: nowrap;
}
.tlist__row { cursor: pointer; }
.tlist__row:hover td { background: var(--surface-2); }
.tlist__stage { cursor: pointer; background: var(--surface-2); }
.tlist__stage .tlist__name { font-weight: 600; }
.tlist__name { white-space: normal; min-width: 220px; }
.tlist__dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: var(--s-2); vertical-align: 1px;
}
.tlist__flag {
  font-size: var(--t-2xs); color: var(--warn); border: 1px solid var(--warn);
  border-radius: var(--radius-full); padding: 0 var(--s-2); margin-left: var(--s-2);
}
.tlist__room { color: var(--text-dim); }
.tlist__status {
  font-size: var(--t-xs); padding: var(--s-1) var(--s-2); border-radius: var(--radius-full); font-weight: 600;
  background: color-mix(in srgb, var(--st-color, #94a3b8) 14%, transparent);
  color: var(--st-color, #94a3b8);
}
.tlist__date { color: var(--text-dim); }
.tlist__date.is-late { color: var(--critical); font-weight: 600; }
.tlist__who { display: flex; gap: var(--s-05); }
.tlist__ava {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--t-2xs); font-weight: 600;
  background: var(--accent-soft); color: var(--accent); border: 1px solid var(--border);
}
.tlist__ava.has-img { overflow: hidden; }
.tlist__more { font-size: var(--t-xs); color: var(--text-dim); align-self: center; }
.tlist__empty { text-align: center; color: var(--text-faint); padding: var(--s-5) 0 !important; }

/* Стили звёзд сложности убраны вместе с самой оценкой (27 августа 2026):
   .starrow, .starbtn, .kcard__stars, .tlist__stars, .tpill--stars. Поле
   в базе и ручка PATCH на месте — см. starsNode в kanban.js.

/* ── Закреплённое на странице проекта ────────────────────────────────────── */

/* Закреплённое: описание проекта, доска в Miro, папка с обмерами, договор,
   памятка команде. Стеной карточек это занимало 145px над задачами — ради
   ссылки с подписью. Теперь строка: значок, название, подпись — в одну высоту. */
/* Закреплённое: плашки одной ширины, но ряд не растягивается. Две штуки
   на всю строку выглядели как незаполненная таблица; теперь они просто
   стоят слева, а свободное место остаётся свободным. */
/* Ширина плашки убывает плавно, а не ступенями.
   Было: ровно 236px, а на 1100 точках — сразу 190. Между этими двумя
   значениями ряд не сужался вовсе, а копил пустое место справа, пока
   очередная плашка не срывалась на вторую строку целиком; на 1100 всё
   разом худело на четверть. Теперь ширина считается от ширины окна и едет
   вместе с ним — ряд теряет плашку не рывком, а по мере того, как ей
   действительно перестаёт хватать места.

   Не `1fr`: плашки не растягиваются на всю строку. Две штуки во всю ширину
   выглядели незаполненной таблицей — свободное место остаётся свободным. */
.pnotes {
  display: grid; gap: var(--s-2); margin-bottom: var(--s-4);
  grid-template-columns: repeat(auto-fill, minmax(0, clamp(168px, 19vw, 236px)));
  align-items: stretch;
}
/* «ещё 4» и «Свернуть» в ряду закреплённого и дедлайнов.
   Не кнопка и не плашка: тихая подпись, которую нажимают. Выглядеть как
   заметка ей нельзя — она не содержимое ряда, а способ показать остальное;
   выглядеть как кнопка тоже: тогда она перетянет внимание с того, ради
   чего ряд и стоит. */
.foldmore {
  background: none; border: none; padding: 0 var(--s-2); cursor: pointer;
  font: inherit; font-size: var(--t-xs); color: var(--text-faint);
  align-self: center; justify-self: start; white-space: nowrap;
  transition: color var(--fast);
}
.foldmore:hover { color: var(--accent); }
.pnote {
  border-radius: var(--radius-lg); padding: var(--s-2) var(--s-3) var(--s-2) var(--s-2);
  font-size: var(--t-sm); border: 1px solid transparent;
  display: flex; align-items: flex-start; gap: var(--s-2);
  min-height: 34px; min-width: 0; width: 100%;
  transition: border-color var(--fast), box-shadow var(--fast);
}
/* Описание проекта: две строки текста без подписи над ними */
.pnote:hover { border-color: rgba(0, 0, 0, .12); box-shadow: var(--shadow-xs); }
[data-scheme='dark'] .pnote:hover { border-color: rgba(255, 255, 255, .16); }
.pnote__open {
  display: inline-flex; align-items: flex-start; gap: var(--s-2); min-width: 0;
  color: inherit; text-decoration: none; font: inherit; cursor: pointer;
  background: none; border: none; padding: 0; text-align: left;
}
.pnote__open:hover { text-decoration: none; }
.pnote__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* Текст идёт под заголовком и занимает до двух строк: в одну строку
   помещалась половина фразы, и закреплённое нельзя было прочитать */
.pnote__body {
  opacity: .75; min-width: 0; overflow: hidden; font-size: var(--t-xs);
  line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow-wrap: anywhere;
}
/* Описание проекта — единственная плашка без заголовка: строка названия
   у неё свободна, значит текста помещается на строку больше. Правило стоит
   после общего намеренно: раньше оно шло выше и общие две строки его
   перебивали — описание обрезалось там, где обрезаться было незачем. */
.pnote__body--lead {
  -webkit-line-clamp: 3; opacity: 1; color: var(--text);
}
button.pnote { font: inherit; cursor: pointer; text-align: left; }
/* Описание проекта — такая же плашка, только шире: одной строкой над задачами
   его переставали замечать, а в настройках оно и вовсе никому не попадалось */
/* Заголовок «О проекте» набран как у остальных плашек: крупнее он читался
   как заголовок раздела, хотя это такая же закреплённая карточка */
.pnote--about { max-width: 460px; }
.pnote--about .pnote__title { font-size: var(--t-base); font-weight: 600; }
/* «+ Заметка» — по размеру слова, а не во всю плашку.
   Раньше она занимала полный столбец сетки и была самым крупным предметом
   в ряду: пустая пунктирная коробка шириной с заметку, но без единого слова
   о проекте. Чем уже экран, тем дороже это место — и тем очевиднее, что
   на нём полезнее стоять настоящей заметке.
   Теперь кнопка сжимается по содержимому и прижата к началу своего столбца,
   а когда ряд перестаёт помещаться в строку, `foldRow` прячет её первой:
   у неё больше нет `data-keep`. Действие при этом не пропадает — «Заметка»
   лежит в меню «…» в шапке проекта. */
.pnote--add {
  background: none; border: 1px dashed var(--border-strong); color: var(--text-dim);
  cursor: pointer; font-family: inherit; gap: var(--s-2);
  display: inline-flex; align-items: center; justify-content: center;
  width: auto; min-width: 0; justify-self: start; padding: 0 var(--s-3);
  white-space: nowrap;
}
/* Плюс стоял выше подписи: у знака своя высота строки, и он всплывал.
   Здесь он выровнен по центру кнопки, как и слово рядом. */
.pnote__plus { display: inline-flex; align-items: center; line-height: 1; font-size: var(--t-md); }
.pnote--add:hover { border-color: var(--accent); color: var(--accent); box-shadow: none; }
/* Цвет стикера считается от полотна темы, а не написан числом.
   Раньше светлая половина держала готовые пастели (#fef9c3, #dbeafe): они
   не знали ни одной из тем, лежали на «Бумаге» тем же холодным жёлтым, что
   и на «Дне», и в тихом интерфейсе горели ярче всего остального на экране.
   Тёмная половина уже считалась подмешиванием — теперь так считаются обе,
   и краски ровно столько, чтобы отличить одну бумажку от другой.

     --pnote-hue   краска стикера; светлым темам — насыщенная (500),
                   тёмным — светлая (400): на тёмном экране густая краска
                   уходит в грязь, на светлом бледная не читается;
     --pnote-tint  сколько краски в заливке — доля от полотна;
     --pnote-ink   сколько краски в подписи. На светлом подпись остаётся
                   тёмной и лишь окрашивается, на тёмном — наоборот. */
:root { --pnote-tint: 16%; --pnote-ink: 42%; }
[data-scheme='dark'] { --pnote-tint: 14%; --pnote-ink: 70%; }

.pnote--yellow, .pnote--lime, .pnote--green, .pnote--teal, .pnote--blue,
.pnote--purple, .pnote--pink, .pnote--red, .pnote--orange,
.stick--yellow, .stick--lime, .stick--green, .stick--teal, .stick--blue,
.stick--purple, .stick--pink, .stick--red, .stick--orange {
  background: color-mix(in srgb, var(--pnote-hue) var(--pnote-tint), var(--raised));
  color: color-mix(in srgb, var(--pnote-hue) var(--pnote-ink), var(--text));
}

.pnote--yellow, .stick--yellow { --pnote-hue: #eab308; }
.pnote--lime, .stick--lime   { --pnote-hue: #84cc16; }
.pnote--green, .stick--green  { --pnote-hue: #22c55e; }
.pnote--teal, .stick--teal   { --pnote-hue: #14b8a6; }
.pnote--blue, .stick--blue   { --pnote-hue: #3b82f6; }
.pnote--purple, .stick--purple { --pnote-hue: #8b5cf6; }
.pnote--pink, .stick--pink   { --pnote-hue: #ec4899; }
.pnote--red, .stick--red    { --pnote-hue: #ef4444; }
.pnote--orange, .stick--orange { --pnote-hue: #f97316; }

[data-scheme='dark'] .pnote--yellow, [data-scheme='dark'] .stick--yellow { --pnote-hue: #facc15; }
[data-scheme='dark'] .pnote--lime, [data-scheme='dark'] .stick--lime   { --pnote-hue: #a3e635; }
[data-scheme='dark'] .pnote--green, [data-scheme='dark'] .stick--green  { --pnote-hue: #4ade80; }
[data-scheme='dark'] .pnote--teal, [data-scheme='dark'] .stick--teal   { --pnote-hue: #2dd4bf; }
[data-scheme='dark'] .pnote--blue, [data-scheme='dark'] .stick--blue   { --pnote-hue: #60a5fa; }
[data-scheme='dark'] .pnote--purple, [data-scheme='dark'] .stick--purple { --pnote-hue: #a78bfa; }
[data-scheme='dark'] .pnote--pink, [data-scheme='dark'] .stick--pink   { --pnote-hue: #f472b6; }
[data-scheme='dark'] .pnote--red, [data-scheme='dark'] .stick--red    { --pnote-hue: #f87171; }
[data-scheme='dark'] .pnote--orange, [data-scheme='dark'] .stick--orange { --pnote-hue: #fb923c; }

/* Серый — не краска, а её отсутствие: берётся прямо у темы */
.pnote--gray, .stick--gray { background: var(--surface-2); color: var(--text); }
.pnote__head { display: inline-flex; align-items: center; gap: var(--s-2); min-width: 0; }
.pnote__title {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1.3;
}
/* Закреплённое целиком — в окне чтения: переносы автора сохраняем */
.pinread { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.55; }
.pnote__badge {
  width: 20px; height: 20px; border-radius: var(--radius-sm); flex: none;
  display: grid; place-items: center; font-size: var(--t-xs); font-weight: 700;
}
.pnote__badge--plain { background: transparent; font-size: var(--t-base); }
/* Место под карандаш и крестик отведено всегда, меняется только видимость:
   пока ширина росла по наведению, плашка раздвигалась под курсором и вся
   строка закреплённого перекладывалась заново — соседи прыгали, а последний
   мог уехать на другую строку. */
.pnote__act {
  border: none; background: none; cursor: pointer; color: inherit; flex: none;
  opacity: 0; width: 18px; height: 20px; overflow: hidden;
  font-size: var(--t-sm); padding: 0; border-radius: var(--radius-sm);
  transition: opacity var(--fast), background var(--fast);
}
.pnote:hover .pnote__act, .pnote:focus-within .pnote__act { opacity: .55; }
.pnote__act:hover { opacity: 1 !important; background: rgba(0, 0, 0, .07); }
/* На сенсорном экране наведения не бывает — правка и открепление видны сразу */
@media (hover: none) {
  .pnote__act { opacity: .5; }
}

/* Цвета стикеров в палитре выбора */
.swatch--note-yellow { background: #fde047; }
.swatch--note-green  { background: #86efac; }
.swatch--note-blue   { background: #93c5fd; }
.swatch--note-pink   { background: #f9a8d4; }
.swatch--note-gray   { background: #d4d4d8; }
.swatch--note-orange { background: #fdba74; }
.swatch--note-red    { background: #fca5a5; }
.swatch--note-purple { background: #c4b5fd; }
.swatch--note-teal   { background: #5eead4; }
.swatch--note-lime   { background: #bef264; }

/* ── Доска, встроенная в страницу проекта ────────────────────────────────── */

.projboard { margin-bottom: var(--s-2); }
/* Внутри страницы проекта у панели и полосы дедлайнов своих полей быть
   не должно: они складывались с полями страницы, и доска с дедлайнами
   стояли с лишним отступом справа от всего остального. */
.projboard .duebar { padding-left: 0; padding-right: 0; }
/* Между закреплённым и дедлайнами воздуха было в две ступени шкалы —
   слишком много для соседних полос одного экрана. Оставили полторы. */
.projboard .duebar { padding-top: var(--s-2); }
/* Колонки стояли на 18 точек внутрь от панели над ними: 16 давало поле
   холста, 2 — сам канбан. Вертикальные края всего на экране совпадали,
   а доска — единственное, ради чего сюда заходят, — стояла с уступом.
   Поле холста снято; двойка канбану нужна (это он прокручивается вбок,
   и без неё срезает тень и рамку выделения у крайних карточек), поэтому
   она гасится отрицательным полем — содержимое встаёт вровень, а место
   под тень остаётся. */
.projboard .board__canvas { padding-left: 0; padding-right: 0; }
.projboard .kanban { margin-left: -2px; margin-right: -2px; }
/* Встроенная в проект доска. Высоту задаёт содержимое, окно ограничивает
   сверху: раньше высота всегда бралась от окна, и на графике из трёх задач под
   последней строкой оставалось пустое поле с прокруткой. Рамки и заливки нет —
   доска лежит прямо на полотне страницы. */
.board--embed {
  height: auto; min-height: 0;
  --board-cap: clamp(420px, calc(100dvh - 300px), 1080px);
  border: none; border-radius: 0; background: transparent;
  display: flex; flex-direction: column;
}

/* Строка умолчания: название, переключатель, счёт кого достанет.
   Счёт стоит справа и тише всего — он не орган управления, а последствие
   того, что стоит слева от него. */
.umolrow {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) 0; border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.umolrow:last-child { border-bottom: none; }
.umolrow__name { flex: 1 1 200px; min-width: 0; font-size: var(--t-sm); }
.umolrow__n { flex: 0 0 auto; text-align: right; min-width: 160px; }
@media (max-width: 700px) {
  .umolrow__name { flex-basis: 100%; }
  .umolrow__n { min-width: 0; }
}

/* ── Строки редакторов структуры (этажи, помещения, типы, приоритеты) ────── */

.structrow { display: flex; gap: var(--s-2); align-items: center; margin-bottom: var(--s-2); }
/* Строка типа задачи: имя сверху, оттенки метки под ним отдельным рядом */
.structrow--type { flex-wrap: wrap; margin-bottom: var(--s-4); }
.swatchrow--tag { flex-basis: 100%; gap: var(--s-1); }
.swatchrow--tag .swatch { width: 20px; height: 20px; }
.structrow .input { flex: 1; }
/* Пара стрелок «выше — ниже» вплотную друг к другу: это один орган из двух
   половин, а не две отдельные кнопки. Между ними и «убрать» — обычный зазор
   строки: убрать и переставить путать нельзя. */
.structrow__move { display: inline-flex; gap: 2px; flex: none; }
.structrow__move .btn { width: 26px; padding: 0; font-size: var(--t-sm); }
.structrow__move .btn:disabled { opacity: .3; }
/* Строка этапа комплектации: название и подсказка — разной длины текст,
   поэтому и места им нужно разное; оттенки уезжают под них своим рядом */
.structrow--stage .stage__name { flex: 1 1 150px; min-width: 130px; }
.structrow--stage .stage__hint { flex: 2 1 240px; min-width: 160px; }
.structrow--stage { padding-bottom: var(--s-2); border-bottom: 1px solid var(--border); }
.colorpick {
  width: 34px; height: 34px; padding: var(--s-05); border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface); cursor: pointer; flex: none;
}
.modal--wide { max-width: 640px; width: min(640px, calc(100vw - 32px)); }

/* Срок, которого нет. Не красный и не чёрный: «без срока» — не беда
   и не обещание, а отсутствие обещания. Тише всего остального в строке. */
.kcard__date.is-none, .kcard__date.is-none .kcard__date-btn { color: var(--text-faint); }

/* ── Узловые точки закупки: план и факт по каждой ────────────────────────── */

.proctochki { display: grid; gap: var(--s-2); }
.proctochki__head, .proctochki__row {
  display: grid; grid-template-columns: 1fr 150px 150px 90px;
  gap: var(--s-2); align-items: center;
}
.proctochki__head {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint);
}
.proctochki__name { font-size: var(--t-sm); color: var(--text-dim); }
/* Отставание: «+3 дня» красным, «в срок» — тише всего. Знак важнее числа,
   поэтому он стоит первым и не отрывается от него переносом. */
.procdelta { font-size: var(--t-xs); font-weight: 600; white-space: nowrap; }
.procdelta.is-late { color: var(--critical); }
.procdelta.is-early { color: var(--ok, var(--text-dim)); }
.procdelta.is-ok { color: var(--text-faint); font-weight: 500; }
@media (max-width: 700px) {
  /* Четыре колонки в телефон не влезают: подпись встаёт своей строкой,
     под ней план и факт рядом — так их и сравнивают. */
  .proctochki__head { display: none; }
  .proctochki__row { grid-template-columns: 1fr 1fr; grid-auto-rows: auto; }
  .proctochki__name { grid-column: 1 / -1; font-weight: 600; color: var(--text); }
  .proctochki__delta { grid-column: 1 / -1; }
}

/* Та же задача без срока на диаграмме: полоса контуром, а не заливкой.
   Даты под ней есть — иначе ей негде стоять, — но они не обещание. */
.bar.is-nodate .bar__body {
  fill: none; stroke: var(--border-strong); stroke-width: 1.5; stroke-dasharray: 4 3;
}
.bar.is-nodate .bar__done { opacity: .35; }
.bar.is-nodate .bar__label { opacity: .7; }

/* Окно заведения задачи: галочка строкой и складка «Подробности».
   Окно открывают десятки раз в день, и всё, что нужно не каждый раз,
   лежит закрытым — иначе четыре поля превращаются в анкету на восемь. */
.checkline {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-sm); color: var(--text-dim);
  margin: calc(-1 * var(--s-2)) 0 var(--s-3); cursor: pointer;
}
.checkline input { margin: 0; }
.newtask__more { margin-top: var(--s-2); }
.newtask__more > summary {
  cursor: pointer; font-size: var(--t-sm); color: var(--text-dim);
  padding: var(--s-2) 0; user-select: none;
}
.newtask__more > summary:hover { color: var(--text); }
.newtask__more[open] > summary { margin-bottom: var(--s-2); }

/* Переключатель разделов проекта живёт слева, вплотную к кнопке добавления,
   и прилипать к правому краю ему незачем — правило ниже писано для
   переключателя вида, который стоит в конце полосы. */
.board__bar .seg--razdel { position: static; box-shadow: none; outline: none; }
/* Значок и подпись рядом: одними значками этот ряд путали с переключателем
   вида, который стоит в той же полосе такими же квадратиками.
   width: auto обязателен — у `.viewseg button` ширина задана в 34 точки под
   один значок, и подпись вылезала из кнопки на соседнюю. */
.seg--razdel button {
  display: inline-flex; align-items: center; gap: var(--s-2);
  width: auto; padding: 0 var(--s-3);
}
.seg--razdel .seg__txt { white-space: nowrap; }
/* Подпись бывает двух длин, и показывается одна. Полная — «Задачи
   по проекту», «График закупок»; короткая — «Задачи», «Закупки».
   Совсем убирать подпись, как было раньше, нельзя: разделов стало три,
   и по одному значку «комплектация» от «закупок» не отличить. */
.seg--razdel .seg__txt--short { display: none; }
@media (max-width: 900px) {
  .seg--razdel .seg__txt--full { display: none; }
  .seg--razdel .seg__txt--short { display: inline; }
}
/* Короткая подпись включается не только шириной окна, но и теснотой самой
   панели: у проекта с комплектацией в ней стоят три раздела, кнопка
   добавления, отбор, четыре вида и «…», и на 1280 точках всё это уже
   не помещалось в строку даже со свёрнутым отбором. Ставит класс
   `подогнать` в app.js — измерением, а не порогом: пороги здесь врут,
   потому что ширина зависит ещё и от того, открыта ли левая колонка. */
.board__bar.is-tight .seg--razdel .seg__txt--full { display: none; }
.board__bar.is-tight .seg--razdel .seg__txt--short { display: inline; }

/* Переключатель вида не должен уезжать при прокрутке панели инструментов:
   в режиме ганта кнопок много, а переключиться обратно нужно всегда */
.board__bar .viewseg {
  position: sticky; right: 0; z-index: 2;
  box-shadow: -10px 0 10px -6px var(--surface), -1px 0 0 var(--surface);
  outline: 4px solid var(--surface);
}

/* Комплектация: архивные позиции приглушены */
.table tr.is-archived td { opacity: .45; }

/* ── Карточки задач вне канбана: главная, «Мои задачи» ───────────────────── */

.cardsgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--s-3);
}
.cardsgrid--home { grid-template-columns: repeat(auto-fill, minmax(265px, 1fr)); align-items: stretch; }
/* Ровный ряд: карточка не ниже трёх строк названия плюс метки и срок */
.cardsgrid--home .kcard--free { min-height: 132px; }

/* Тот же список строками: карточка растягивается на всю ширину и складывается
   в одну линию — название слева, метки следом, срок и статус у правого края.
   Задач видно вдвое больше, а содержимое то же самое. */
.cardsgrid--home.is-rows { grid-template-columns: 1fr; gap: var(--s-2); }
/* Строка в две линии: сверху название во всю ширину, снизу метки и срок.
   В одну линию метки начинали обрезаться на первом же длинном названии, а их
   там до пяти — проект, этаж, помещение, тип, приоритет. Высота одинаковая у
   всех строк: разнобой в списке читается хуже, чем лишний воздух. */
.is-rows .kcard--free {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: 'title title' 'tags meta';
  align-items: center; gap: var(--s-1) var(--s-3);
  padding: var(--s-3) var(--s-4); min-height: 74px; min-width: 0;
}
.is-rows .kcard__title { grid-area: title; align-items: center; min-width: 0; }
/* Строка всегда одна и уже выровнена по центру — полразницы здесь сдвинули бы
   название вниз относительно кружка вместо того, чтобы совместить их */
.is-rows .kcard__title .kcard__name { margin-top: 0; }
/* Название в строке обрезается многоточием, а не обрубается на середине
   буквы. display:-webkit-box из общей карточки здесь мешает: в сафари
   с ним многоточие не рисуется вовсе, и на айфоне название просто обрывалось. */
.is-rows .kcard__name {
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.is-rows .kcard__stage { display: none; }
/* Метки второй строки начинаются там же, где название, а не под кружком.
   Название сдвинуто кружком хода, метки — нет, и вторая строка уезжала
   влево, ломая левый край карточки. Отступ ровно на кружок с зазором и
   только там, где кружок есть: в «Поручил» и «Наблюдаю» его нет, и сдвигать
   нечего. */
.is-rows .kcard--free:has(.stepbtn) .kcard__tags {
  padding-left: calc(var(--step-size) + var(--s-2));
}
/* Метки в строке: раньше они жались в один ряд и обрезались все разом —
   первым же уезжал проект, а он-то и различает одинаковые названия.
   Теперь ряд переносится, проект не сжимается, а лишние метки уходят
   за край по одной, начиная с наименее важной. */
.is-rows .kcard__tags {
  grid-area: tags; margin-top: 0; min-width: 0; flex-wrap: wrap; overflow: hidden;
  max-height: 34px;
}
.is-rows .ktag--project { flex: none; max-width: none; }
.is-rows .kcard--free .kcard__meta {
  grid-area: meta; margin: 0; padding-top: 0; flex-wrap: nowrap;
}
/* Лица в строке крупнее, чем в карточке: строка 78 пикселей высотой, и всю
   её вторую половину занимал кружок в 22 — ровно столько, сколько он сам
   и задавал. Инициалы в нём читались только вблизи, а по ним в этом списке
   и узнают, чья задача. 28 — ступень, на которой две буквы видно с рабочего
   расстояния, и она всё ещё меньше кружка хода: главное в строке — действие,
   а не лицо.

   В режиме карточек кружок остаётся мелким: там своё лицо у каждой карточки,
   и крупные кружки превратили бы сетку в ряд портретов. */
.is-rows .kcard__who .avatar { width: 28px; height: 28px; }
.is-rows .kcard__who .avatar + .avatar { margin-left: -8px; }
.is-rows .kcard__who-n { font-size: var(--t-xs); }
.is-rows .kcard--add { min-height: 0; padding: var(--s-3); }
/* Черта приземления при перетаскивании ложится поперёк, а не вдоль */
.is-rows .kcard--free.is-insert-before::before,
.is-rows .kcard--free.is-insert-after::after {
  left: 0; right: 0; width: auto; height: 3px; top: auto; bottom: auto;
}
.is-rows .kcard--free.is-insert-before::before { top: calc(-1 * var(--s-1) - 1px); }
.is-rows .kcard--free.is-insert-after::after { bottom: calc(-1 * var(--s-1) - 1px); }
@media (max-width: 760px) {
  /* На телефоне метки и срок встают друг под друга: на две колонки места нет */
  .is-rows .kcard--free {
    grid-template-columns: minmax(0, 1fr); grid-template-areas: 'title' 'tags' 'meta';
  }
  .is-rows .kcard--free .kcard__meta { justify-content: flex-start; }
}
.kcard--free {
  width: 100%; text-align: left; font: inherit; color: inherit; cursor: pointer;
  display: flex; flex-direction: column; gap: var(--s-05);
}
.kcard--free .kcard__meta { margin-top: auto; padding-top: var(--s-2); }

/* ── Задачи прямо на странице проекта, без «рамки отдельной доски» ───────── */

/* Панель с видами и отборами — своей полосой на подложке: так видно, что это
   органы управления доской, а не ещё один ряд содержимого.
   Полей у неё какое-то время не было вовсе, и синяя «+ Задача» упиралась
   в край полосы — кнопка на подложке без поля слева выглядит обрубленной.
   Поля возвращены внутрь полосы: наружу она по-прежнему встаёт вровень
   с закреплённым и дедлайнами, а кнопкам есть куда дышать. */
.board--embed .board__bar {
  padding: var(--s-2) var(--s-3);
  background: none; border: 1px solid var(--border);
  border-radius: var(--radius); margin-bottom: var(--s-2);
}
.projboard { margin-bottom: 0; }

/* ── Интерактивность: нажатия и наведения ощущаются живыми ──────────────── */

.btn { transition: background var(--fast), border-color var(--fast), transform var(--fast), box-shadow var(--fast); }
.btn:active { transform: translateY(1px); }
.btn--primary:hover { box-shadow: 0 4px 14px var(--accent-ring); }
.kcard--free:hover { box-shadow: var(--shadow-md); border-color: var(--border-strong); }
.pnote { transition: transform var(--fast), box-shadow var(--fast); }
.pnote:hover { box-shadow: var(--shadow-md); filter: brightness(.985); }
.teamchip { transition: border-color var(--fast), box-shadow var(--fast); }
.teamchip:hover { border-color: var(--border-strong); box-shadow: var(--shadow-xs); }
.tab { transition: color var(--fast), border-color var(--fast); }
.tab:hover { color: var(--text); }
.tlist__row td { transition: background var(--fast); }
.feedrow { transition: background var(--fast); }

/* Видимый фокус с клавиатуры — доступность без охоты за элементами */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.kcard:focus-visible, .kcard--free:focus-visible { outline-offset: 0; }

/* ── Наш софт: витрина программ бюро ─────────────────────────────────────── */

.softgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--s-3);
  align-items: stretch;
}
/* Для каких систем есть программа — значками справа от кнопок. Словами
   («Браузер», «Windows», «macOS») этот ряд выходил длиннее самих кнопок
   и читался раньше их, хотя отвечает на второй вопрос, а не на первый. */
.softplats { display: inline-flex; gap: var(--s-2); align-items: center; margin-left: auto; }
.softplat {
  display: inline-flex; align-items: center; color: var(--text-faint);
  transition: color var(--fast);
}
.softcard:hover .softplat { color: var(--text-dim); }
/* «Новое» — метка, которую ставят руками */
.softcard__new {
  margin-left: var(--s-2); font-size: var(--t-2xs); font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em;
  color: var(--ok); background: var(--ok-soft);
  border-radius: var(--radius-sm); padding: 1px var(--s-2);
  vertical-align: .1em;
}
/* Лицензия — строкой под кнопками: это не действие, а условие, при котором
   действие имеет смысл. Раньше строка стояла вместо кнопки «Скачать»,
   и бесплатную сборку было не достать. */
.softcard__lic {
  font-size: var(--t-xs); color: var(--text-faint);
  margin-top: var(--s-2); line-height: 1.4;
}

/* Кнопка сохранения профиля не уезжает за край длинной формы */
.profsave {
  position: sticky; bottom: 0; padding: var(--s-3) 0 0;
  background: linear-gradient(to bottom, transparent, var(--surface) 40%);
}

/* Полосы-разделители в админке: настройки шли одним столбцом от важного
   до косметики */
.adminband {
  display: flex; align-items: baseline; gap: var(--s-3);
  margin: var(--s-6) 0 var(--s-3); padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--border);
}
.adminband:first-child { margin-top: 0; }
.adminband h2 { margin: 0; font-size: var(--t-md); }

/* Полоса действий над несколькими задачами: появляется, только когда что-то
   отмечено — постоянная панель занимала бы место ради недельного случая */
.tbulk {
  position: sticky; bottom: var(--s-3); z-index: 5;
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  margin-top: var(--s-3); padding: var(--s-2) var(--s-3);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: var(--shadow-md);
}
.tbulk__n { font-weight: 600; font-size: var(--t-sm); margin-right: var(--s-2); }
.tlist__pick { width: 32px; text-align: center; }
.tlist__pick input { cursor: pointer; }

/* Перетаскивание строк таблицы: на двадцати людях стрелками не наездишься */
.draghandle { cursor: grab; color: var(--text-faint); user-select: none; font-size: var(--t-sm); }
.draghandle:active { cursor: grabbing; }
tr.is-drop td { box-shadow: inset 0 2px 0 var(--accent); }

.softcheck {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-sm); color: var(--text-dim); cursor: pointer;
}
.softcard {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--s-4) var(--s-4) var(--s-3);
  display: flex; flex-direction: column; gap: var(--s-3);
  box-shadow: var(--shadow-xs);
  transition: border-color var(--fast), box-shadow var(--fast), transform var(--fast);
  /* Описания у программ разной длины, и низ ряда был рваным: карточки тянутся
     на высоту ряда, кнопки прижимаются к низу */
  height: 100%;
}
.softcard__acts { margin-top: auto; }
.softcard:hover {
  border-color: var(--soft-color, var(--border-strong));
  box-shadow: var(--shadow-md); border-color: var(--border-strong);
}
.softcard.is-hidden { opacity: .5; }
/* Значок держится верха: описания у программ разной длины, и значок,
   выровненный по центру, гулял по карточке вверх-вниз от соседа к соседу */
.softcard__head { display: flex; gap: var(--s-3); align-items: flex-start; }
.softcard__icon {
  flex: none; border-radius: var(--radius); overflow: hidden;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--soft-color, var(--accent)) 12%, transparent);
}
/* contain, а не cover: у скачанных значков поля по краям разные, и обрезка
   съедала бы у одних рамку, у других — часть знака */
.softcard__icon img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* Светлая подложка под значком в тёмной теме. Логотипы у своих программ
   тёмные и без фона: в «Ночи» на их месте оставались пустые квадраты.
   Подложка светлая всегда — так знак выглядит одинаково в обеих темах. */
[data-scheme='dark'] .softcard__icon:not(.is-letter):not(.is-mark) {
  background: #f3f5f8;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .08);
}
.softcard__icon.is-letter {
  color: var(--soft-color, var(--accent));
  font-weight: 700; font-size: var(--t-xl); line-height: 1;
}
/* Знак программы рисует свою подложку сам — общая плашка под ним лишняя */
.softcard__icon.is-mark { background: none; }
.softcard__icon.is-mark svg { width: 100%; height: 100%; display: block; }
.softcard__name { font-weight: 600; font-size: var(--t-md); }
.softcard__desc {
  font-size: var(--t-sm); color: var(--text-dim); margin-top: var(--s-05); line-height: 1.35;
}
.softcard__acts { display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; }
.softcard__acts .spacer { flex: 1 1 auto; }
.softcard__ver {
  margin-left: var(--s-2); font-family: var(--mono); font-size: var(--t-2xs);
  color: var(--text-faint); white-space: nowrap; font-weight: 400;
}
a.btn { text-decoration: none; display: inline-flex; align-items: center; }

/* ── Система направления «Полночь. Лазурь» ───────────────────────────────
   Архитектурная нотка живёт не в картинках, а в наборе правил: числа
   моноширинные, как размерные надписи на листе; подписи разделов идут
   в разрядку, как в штампе чертежа; форма скруглений одна на всё. */

/* Одна шкала формы: контейнеры и кнопки 8 px, метки 6 px */
.btn, .card, .bcard, .kcard, .kcard--free, .input, .select, .textarea,
.kcol, .softcard, .modal, .sheet { border-radius: var(--radius); }
.chip, .ktag, .kcard__prio, .tlist__status { border-radius: var(--radius-sm); }

/* Подписи — как надписи в штампе листа. Заголовков разделов здесь нет:
   когда заголовок блока набран так же, как подпись поля, между ними нет
   разницы по весу и структура страницы перестаёт читаться. Приём остаётся
   на том, что он и подписывает: колонки, шапки таблиц, поля форм. */
.kcol__name, .tlist__table th, .field__label, .vstat__label {
  text-transform: uppercase;
  letter-spacing: .1em;
  font-size: var(--t-2xs);
  font-weight: 600;
}

/* Числа — размерные надписи: моноширинные, выровненные по разрядам.

   Крупные значения графика работы (`.vstat__value`) сюда не входят: им нужна
   моноширинность, но не этот кегль. Пока они стояли в общем списке, правило
   ниже по файлу перебивало их собственные 28 пикселей на 13 — «сделать
   информацию про отпуск крупнее» было записано в стилях и не работало
   ни дня. Семейство и выключку они получают отдельной строкой. */
.kcard__date, .tlist__date, .kcard__pct,
.hrow__right, .feedrow__time, .softcard__ver {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-mono);
  letter-spacing: -.01em;
}
.vstat__value { font-family: var(--mono); letter-spacing: -.01em; }
/* Срок на карточке — на ступень мельче остальных размерных надписей.

   Моноширинный при том же кегле читается крупнее пропорционального: шире
   межбуквенное, ровнее очко. Рядом с меткой в тринадцать пикселей срок тем же
   кеглем выглядел крупнее её — хотя метка про саму задачу, а срок только
   подпись при ней. Двенадцать выравнивают их на глаз.

   Правило стоит после общего, а не рядом с остальными про срок: там оно
   объявлено выше по файлу и общее его перебивало — размер оставался прежним,
   а я считал, что уменьшил. */
.kcard__date { font-size: var(--t-2xs); }
/* Предлог при сроке — подпись, а не кнопка. Пропорциональным шрифтом:
   моноширинный нужен цифрам, чтобы столбец дат стоял ровно, а слову «до»
   он придаёт вид кода. */
.kcard__date-lead {
  font-family: var(--font); color: var(--text-faint); margin-right: 4px;
}
.kcard__date.is-late .kcard__date-lead { color: var(--critical); }

.kcard { padding: var(--s-3) var(--s-3); }
/* Тот же кегль и то же начертание, что и выше: правило стоит ниже
   и перебивало бы их. Интерлиньяж 1.35 совпадает с расчётом отступа
   в `.kcard__title .kcard__name`, которым название совмещают с кружком хода. */
.kcard__name { font-size: var(--t-base); line-height: 1.35; font-weight: 500; }
/* Колонка канбана — неглубокий жёлоб под полотном, а не отдельная плита.

   Она была залита --surface-2, а это ступень, придуманная для полей ввода
   и шапок таблиц: между колонкой и лежащей на ней карточкой набегало до
   1,52 раза по светлоте (Periwinkle), и доска читалась не как лист с
   карточками, а как ряд серых плит, на которых что-то белеет.

   Теперь у колонки свой токен --well: ступень вниз от полотна (1,06–1,08),
   карточка — ступень вверх (1,07). Между колонкой и карточкой выходит
   1,14–1,16 во всех шестнадцати темах.

   Глубину жёлоба подбирали дважды. Сперва взяли 1,05 — и колонка слилась
   с полотном: доска стала ровным листом, на котором карточки висят сами
   по себе. 1,08 читается выемкой, но не плитой. У «Дня» и нескольких
   тёмных тем жёлоб упирается в цвет фона приложения — это не совпадение,
   а предел: глубже в этих палитрах просто нет места, и колонка оказывается
   ровно тем же тоном, что фон за островом. Выглядит как отверстие в листе,
   и это верно по смыслу. */
.kcol { background: var(--well); border: none; }
.kcol__body { gap: var(--s-2); }

/* Модульная линия под шапкой страницы задаёт ритм всему листу */
.page__head { border-bottom: 1px solid var(--border); padding-bottom: var(--s-3); }
.page__head h1 { font-weight: 600; letter-spacing: -.025em; }

/* Знак приложения строится из тех же токенов: акцент один, отдельного
   фиолетового у логотипа больше нет. Правило само по себе не нужно — цвет
   плитки задан выше, — но записать его здесь сокращением `background:`
   нельзя: оно стирает три слоя, которыми нарисованы штрихи, и знак
   превращается в пустой квадрат. */

/* ── Выбор значка заметки ────────────────────────────────────────────────── */

.iconpick {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: var(--s-2);
}
.iconpick__item {
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-2) var(--s-2); border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text); cursor: pointer;
  font-size: var(--t-sm); text-align: left; min-width: 0;
  transition: border-color var(--fast), background var(--fast);
}
.iconpick__item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.iconpick__item:hover { border-color: var(--border-strong); }
.iconpick__item.is-active { border-color: var(--accent); background: var(--accent-soft); }
.iconpick__item .pnote__badge { flex: none; }
/* Знаки сервисов — плитками в одну клетку: подпись у каждого занимала строку
   ради того, что и так узнаётся. Первый, «по ссылке», подписан — он объясняет,
   почему значок иногда меняется сам. */
.iconpick { grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); }
.iconpick__item--auto { grid-column: span 3; }
.iconpick__item--mark { justify-content: center; padding: var(--s-2); }
.pnote__badge--mark { background: none; padding: 0; overflow: hidden; border-radius: var(--radius-sm); }
.pnote__badge--mark svg,
.pnote__badge--mark img { width: 100%; height: 100%; display: block; object-fit: contain; }


/* ── Адаптивность: от телефона до большого монитора ──────────────────────── */

/* Полосе прокрутки вбок место только там, где вкладок и вправду много:
   колонки канбана сжимаются до минимума раньше, чем появится полоса. */
.kanban { scrollbar-width: thin; }
.kanban::-webkit-scrollbar { height: 8px; }
.kanban::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: var(--radius-sm);
}
.kanban::-webkit-scrollbar-track { background: transparent; }

/* Холсты доски: диаграмма, канбан и таблица. На отдельном экране доски они
   тянутся во всю высоту окна, внутри проекта — по содержимому (см. ниже). */
.board__canvas { flex: 1; min-height: 0; position: relative; }
.kanban-host, .list-host { overflow: auto; padding: var(--s-4); }

.board--embed .board__canvas { flex: none; }
/* Шапка со шкалой плюс строки, но не выше отведённого. Высоту считает скрипт
   по остатку окна: пока у страницы и у диаграммы были свои полосы прокрутки,
   колесо мыши работало то на одну, то на другую. */
.board--embed .gantt {
  /* Полоса прокрутки лежит поверх содержимого и накрывала последнюю строку:
     на графике из одной задачи она закрывала её наполовину, и прочесть было
     нечего. Поэтому её рост прибавлен к высоте, а совсем короткому графику
     задан низ — шапка и две строки, чтобы задача не жалась к краям. */
  height: min(var(--board-cap),
              max(148px, calc(52px + var(--rows, 6) * var(--row-h, 34px) + 15px)));
}
/* Диаграмма во весь экран: с ней работают подолгу, и остальной интерфейс
   в это время только отнимает высоту */
.board--full {
  position: fixed; inset: 0; z-index: 60; margin: 0; border-radius: 0;
  background: var(--surface); display: flex; flex-direction: column;
  padding-top: env(safe-area-inset-top);
}
.board--full .board__bar { flex: none; }
.board--full .board__canvas { flex: 1; min-height: 0; }
.board--full .gantt { height: 100%; max-height: none; }
body.has-boardfull { overflow: hidden; }

/* Типы задач словами: их у проекта три-четыре, место в панели есть, а под
   воронкой приходилось открывать список даже затем, чтобы посмотреть,
   включён ли отбор */
.typebar { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; min-width: 0; }
.typechip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  height: 30px; padding: 0 var(--s-3); border-radius: var(--radius-full);
  border: 1px solid transparent; background: none; cursor: pointer;
  font: inherit; font-size: var(--t-sm); color: var(--text-dim); white-space: nowrap;
  transition: background var(--fast), color var(--fast);
}
.typechip:hover { background: var(--surface-2); color: var(--text); }
.typechip__dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--type-color, var(--text-faint));
}
.typechip.is-on {
  background: color-mix(in srgb, var(--type-color, var(--accent)) 16%, transparent);
  color: var(--text); font-weight: 600;
}
.typechip.is-on:first-child { background: var(--surface-3); }
/* Свёрнутый отбор: воронка, название выбранного типа и треугольник.
   Показывается вместо ряда слов, когда ряд перестаёт помещаться в строку
   панели, — см. `подогнать` в app.js.

   Включённый отбор красится общим правилом `.btn.is-on` — тем же, что
   у «показать закрытые» и у прочих выключателей: свёрнутый отбор обязан
   говорить, что часть задач сейчас скрыта, и говорит он это так же, как
   все остальные включённые кнопки приложения. */
.typepick { display: inline-flex; align-items: center; gap: var(--s-2); }
/* Панель меряется на мгновение: перенос запрещаем и смотрим, вылезает ли
   содержимое за ширину. Видимого мигания нет — между добавлением класса
   и снятием браузер не перерисовывает. */
.board__bar.is-measuring { flex-wrap: nowrap; overflow: hidden; }
/* Канбан и список тянутся по содержимому: вертикаль листает страница целиком.
   Ограничение по высоте оставлено только диаграмме — ей нужна своя область с
   закреплённой шкалой. Полоса прокрутки вбок остаётся у самого канбана. */
.board--flow .kanban-host, .board--flow .list-host { overflow: visible; max-height: none; }
.board--flow .board__canvas { flex: none; }
.board--flow { height: auto; }

/* Планшет и узкий ноутбук: карточек в ряд меньше, колонки уже */
@media (max-width: 1100px) {
  .kcol { min-width: 200px; }
  .cardsgrid { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
  .softgrid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
  /* Правила для `.pnotes` здесь больше нет: ширина плашки считается
     от ширины окна и сужается плавно, без ступени на 1100 точках. */
}

/* Большой монитор: шире контейнер, крупнее карточки, больше воздуха */
@media (min-width: 1700px) {
  .cardsgrid { grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); }
  .board--embed { --board-cap: clamp(520px, calc(100dvh - 280px), 1240px); }
}

/* Телефон: колонка канбана почти во весь экран, таблицы листаются, ничего
   не вылезает за край страницы */
@media (max-width: 760px) {
  .kcol { flex: 0 0 82vw; min-width: 0; max-width: 82vw; max-height: none; }
  .board--embed { --board-cap: clamp(380px, calc(100dvh - 300px), 900px); }
  .cardsgrid, .cardsgrid--home { grid-template-columns: 1fr; }
  .softgrid, .pnotes { grid-template-columns: 1fr; }
  .row { flex-direction: column; gap: 0; }
  .verdict, .system { grid-template-columns: 1fr; }
  .iconpick { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
  .tlist { margin: 0 -12px; padding: 0 var(--s-3); }
}

/* Ничто не имеет права раздвигать страницу вбок: длинные ссылки в заметках,
   широкие таблицы и подписи переносятся или прокручиваются внутри себя */
body { overflow-x: hidden; }
.main { max-width: 100%; }   /* предел ширины .page задан выше и не снимается */
.softcard__desc, .feedrow__text { overflow-wrap: anywhere; }


/* ── Телефон: экран проекта ──────────────────────────────────────────────
   Заголовок, заметки и панель инструментов рассчитаны на широкий экран.
   На узком они съедали всю высоту до задач, ради которых экран и открывают.
   Блок держим в конце файла: он правит то, что объявлено выше. */
@media (max-width: 760px) {
  .page--wide { padding-left: var(--s-3); padding-right: var(--s-3); }
  /* Заголовок проекта: 23px на 375px давал перенос на две-три строки */
  .page--wide .page__head h1 { font-size: var(--t-lg); }
  .page__head { gap: var(--s-2); margin-bottom: var(--s-3); }

  /* Закреплённое листается вбок: столбиком оно отодвигало задачи за экран —
     четыре заметки давали семьсот пикселей до первой задачи, а за задачами
     в проект и заходят.

     Полоса задаётся сеткой, а не flex: `.pnotes` — это grid, и `flex-wrap`
     на нём не значит ничего. Правило с `flex-wrap` здесь и стояло, столбик
     держался соседним `grid-template-columns: 1fr`, и вбок ничего
     не листалось. Проверять такое надо снимком, а не чтением.

     Поля наружу и обратно внутрь — чтобы полоса начиналась и обрывалась
     у самого края экрана, а не в рамке поля страницы: обрыв у края и есть
     то, что говорит «здесь листается». */
  .pnotes {
    grid-auto-flow: column; grid-template-columns: none;
    /* Ровно 68vw, а не minmax(0, 68vw): с нулевым минимумом столбцы
       ужимались под ширину экрана вместо того, чтобы выехать за него,
       и от заметки оставался один значок. */
    grid-auto-columns: 68vw;
    overflow-x: auto;
    margin: 0 calc(-1 * var(--s-3)) var(--s-3);
    padding: 0 var(--s-3) var(--s-1);
    scrollbar-width: none;
  }
  .pnotes::-webkit-scrollbar { display: none; }
  /* На узком заголовок занимает свою строку целиком, а статус и шестерёнка
     встают под ним парой. Иначе статус помещался рядом с названием, а
     шестерёнку выбрасывало на третью строку в одиночестве. */
  .page--wide .page__head .spacer { display: none; }
  .page__head { row-gap: var(--s-2); }
  .page--wide .page__head h1 { flex: 1 0 calc(100% - 38px); }

  /* Панель инструментов — ровно две строки, и обе осмысленные.
     
     Переносится, а не листается вбок: на 375px в строку помещалось полторы
     кнопки, а приклеенный к правому краю переключатель вида наезжал на то,
     что под ним. Но простого переноса не хватило — панель расползалась
     на четыре ряда: «+ Задача» одна, отборы одни, переключатель один
     и «…» один. Четыре ряда по одному предмету читаются не как панель,
     а как обломки вёрстки.
     
     Теперь порядок задан: сверху то, чем распоряжаются, — завести задачу,
     сменить вид, открыть «ещё»; снизу отдельной строкой отборы, которые
     листаются вбок, потому что типов бывает пять и в ширину они не влезут
     никогда. */
  .board__bar {
    flex-wrap: wrap; overflow-x: visible;
    padding-top: var(--s-2); padding-bottom: var(--s-2); gap: var(--s-2);
    align-items: center;
  }
  /* «+ Задача» тянется и выталкивает остальное первой строки вправо.
     Исключение из соседнего правила прописано явно: у `.btn--primary`
     и `.btn` одинаковая сила, и без :not() побеждало бы то, что ниже
     по файлу, — то есть порядок задавался бы случайно. */
  /* Разделы проекта — первой строкой во всю ширину.

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

     Три кнопки делят ширину поровну: подписи у них короткие («Задачи ·
     Комплектация · Закупки»), и ряд равных кнопок читается переключателем
     сразу, без вглядывания. */
  .board__bar > .seg--razdel { order: 0; flex: 1 0 100%; }
  .board__bar > .seg--razdel button { flex: 1 1 0; justify-content: center; }
  .board__bar > .btn--primary { order: 1; flex: 1 1 auto; }
  .board__bar > .seg:not(.seg--razdel),
  .board__bar > .btn:not(.btn--primary) { order: 2; flex: none; }
  /* Отборы — своей строкой во всю ширину, листаются вбок */
  .board__bar > .typebar {
    order: 9; flex: 1 0 100%; flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: none; padding-bottom: 2px;
  }
  .board__bar > .typebar::-webkit-scrollbar { display: none; }
  .board__bar > .typebar > * { flex: none; }
  .board__bar .spacer { display: none; }
  .board__bar .viewseg {
    position: static; margin-left: auto;
    box-shadow: none; outline: none;
  }
  .board__bar .bar__sep { display: none; }
  .board--embed { --board-cap: clamp(360px, calc(100dvh - 240px), 900px); }

  /* Цели нажатия. Палец не мышь: всё, что нажимается, должно быть не меньше
     44px по обеим сторонам. Кнопки честно подрастают, а хлебная крошка
     добирает зону невидимой подложкой — иначе строка над заголовком
     раздувается вдвое ради ссылки в одно слово. */
  .board__bar .btn,
  .board__bar .seg button,
  .viewseg button,
  .searchbtn,
  .topbar button,
  .tab {
    min-height: 44px;
  }
  .board__bar .btn, .viewseg button, .topbar button { min-width: 44px; }
  .crumbs button { position: relative; }
  .crumbs button::after {
    content: ''; position: absolute; left: 0; right: 0;
    top: 50%; height: 44px; transform: translateY(-50%);
  }
}

/* ── Рабочий экран: задачи и стопка заметок рядом ────────────────────────── */
/* На широком мониторе справа от задач остаётся пустое поле — туда и уходят
   объявления и записки. На узком колонка встаёт под задачами: сначала работа. */
.homewrap { display: grid; gap: var(--s-7); align-items: start; }
@media (min-width: 1240px) {
  /* Слева работа и под ней лента, справа во всю высоту — заметки */
  .homewrap {
    grid-template-columns: minmax(0, 1fr) 316px;
    grid-template-areas: 'work rail' 'feed rail';
  }
  /* Свёрнутая правая колонка — полоса со знаком, как и левая: колонка,
     пропавшая без следа, не сообщает, чем её вернуть. */
  .homewrap.is-railfolded { grid-template-columns: minmax(0, 1fr) 26px; }
  .homewrap > .home { grid-area: work; }
  .homewrap > .rail { grid-area: rail; }
  .homewrap > .homefeed { grid-area: feed; }
}
.homefeed { min-width: 0; }
/* Колонка задач — полноправный элемент сетки: без min-width её содержимое
   (карточки в три ряда) распирает колонку и наезжает на заметки */
.homewrap > .home { min-width: 0; }
.home > * { min-width: 0; }
/* Таблица «моей работы»: то же самое строками со столбцами.
   Строки живые: кружок хода, статус, срок и лица работают так же, как
   на карточке. В задачу ведут название и проект — не вся строка целиком,
   иначе нажать на что-то внутри неё было бы нельзя. */
.htbl { background: var(--surface); border-radius: var(--radius); overflow: hidden; }
.htbl__row { transition: background var(--fast); }
.htbl__row:hover { background: var(--surface-2); }
.htbl__row:last-child td { border-bottom: none; }
.htbl__cell { cursor: pointer; }
.htbl__name { font-weight: 500; }
.htbl__dim { color: var(--text-dim); }
/* Столбец хода узкий: в нём один кружок или одна плашка */
.htbl__step { width: 1%; white-space: nowrap; padding-right: 0; }
.htbl__due { white-space: nowrap; }
/* Просроченный срок — цветом, как везде в приложении */
.htbl td.is-late { color: var(--critical); font-weight: 500; }
.htbl__who { width: 1%; white-space: nowrap; }
.htbl__who .avatar { margin-left: -6px; }
.htbl__who .avatar:first-child { margin-left: 0; }

.rail { display: flex; flex-direction: column; gap: var(--s-3); min-width: 0; }
/* Складка правой колонки — та же, что у левой, и по той же причине: кому
   объявления и записки сейчас не нужны, тот убирает их и остаётся с одной
   работой. Знак стоит в строке первого заголовка, у правого края — своей
   строкой он держал пустое поле над колонкой, которую сворачивают ради места. */
.rail__fold {
  margin-left: auto; flex: none; width: 26px; height: 26px;
  display: grid; place-items: center;
  border: none; border-radius: var(--radius-sm); background: none;
  color: var(--text-faint); cursor: pointer;
  transition: background var(--fast), color var(--fast);
}
.rail__fold:hover { background: var(--surface-2); color: var(--text); }
/* Развёрнутая колонка складывается вправо, свёрнутая раскрывается влево */
.rail__fold .ic { transform: rotate(180deg); }
.rail__fold.is-folded .ic { transform: none; }
/* От свёрнутой колонки остаётся первая строка заголовка, а от неё — складка */
.rail.is-folded > *:not(.rail__head) { display: none; }
.rail.is-folded .rail__head:not(:has(.rail__fold)) { display: none; }
.rail.is-folded .rail__head { margin: 0; }
.rail.is-folded .rail__head > *:not(.rail__fold) { display: none; }
.rail.is-folded .rail__fold { margin-left: 0; }
/* Та же полоса, что у «Моей работы»: одна высота, одно выравнивание,
   один кегль заголовка — тогда базовые линии сходятся сами */
/* Между подписью раздела и его содержимым нужен воздух: вплотную заголовок
   читался как первая строка списка. На телефоне поле ужимается ниже. */
.rail__head {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: 30px; margin: var(--s-5) 0 var(--s-3);
}
.rail__head:first-child { margin-top: 0; }
.rail__head h2 {
  font-size: var(--t-md); font-weight: 600; color: var(--text); letter-spacing: -.01em;
}
.sticks { display: flex; flex-direction: column; gap: var(--s-2); }
/* В офисах бумажки лежат рядом, а не столбиком: там их бывает много,
   а колонка в узкой правой полосе — это про рабочий экран */
.sticks--wide {
  flex-direction: row; flex-wrap: wrap; align-items: flex-start;
}
.sticks--wide .stick { width: 240px; }
.stick { position: relative; }
/* Ссылка и удаление — значками в углу бумажки, по наведению */
.stick__acts { position: absolute; top: var(--s-1); right: var(--s-1); display: flex; gap: 2px; }
.stick__act {
  border: none; background: none; cursor: pointer; color: inherit;
  width: 22px; height: 22px; padding: 0; border-radius: var(--radius-sm);
  font-size: var(--t-2xs); opacity: 0; transition: opacity var(--fast), background var(--fast);
}
.stick:hover .stick__act, .stick:focus-within .stick__act { opacity: .6; }
.stick__act:hover { opacity: 1; background: rgba(0, 0, 0, .08); }
@media (hover: none) { .stick__act { opacity: .5; } }
.stick__link {
  display: block; margin-top: var(--s-2); font-size: var(--t-2xs);
  color: inherit; opacity: .75; text-decoration: underline; word-break: break-all;
}
.stick__url {
  width: 100%; margin-top: var(--s-2); font: inherit; font-size: var(--t-sm);
  border: none; border-bottom: 1px solid currentColor; background: none;
  color: inherit; padding: 2px 0;
}
.stick__url:focus { outline: none; }

/* Стикер: бумажка, а не карточка — без рамки, с мягким углом и своей краской */
.stick {
  border-radius: var(--radius-lg); padding: var(--s-3) var(--s-4);
  font-size: var(--t-base); line-height: 1.45; text-align: left;
  border: 1px solid transparent; box-shadow: var(--shadow-xs);
  transition: transform var(--fast), box-shadow var(--fast);
}
.stick[tabindex]:hover { box-shadow: var(--shadow-md); cursor: text; }
/* Длинная заметка не должна растягивать колонку на два экрана: показываем
   первые строки, остальное листается внутри самой бумажки */
.stick__body { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 15em; overflow: auto; }
.stick__input { max-height: 22em; overflow-y: auto; }
.stick__who { font-size: var(--t-2xs); opacity: .7; margin-top: var(--s-2); }
.stick__input {
  min-height: 96px;
  width: 100%; border: none; background: none; color: inherit;
  font: inherit; line-height: 1.45; resize: none; outline: none; padding: 0;
  overflow: hidden;
}
.stick__colors { display: flex; gap: var(--s-1); margin-top: var(--s-2); }
.stick__colors .swatch { width: 18px; height: 18px; }
.stick.is-editing { box-shadow: var(--shadow-md); }
.stick--add {
  border: 1px dashed var(--border-strong); background: none; color: var(--text-faint);
  cursor: pointer; font: inherit; font-size: var(--t-sm); padding: var(--s-3) var(--s-4);
  box-shadow: none;
}
.stick--add:hover { border-color: var(--accent); color: var(--accent); }
/* Краски заметок — там же, где краски объявлений: это один набор бумажек,
   и разъезжаться им нельзя. См. «Цвет стикера» выше. */

/* Описание проекта строкой под названием: два ряда, дальше многоточие —
   развёрнутый текст живёт в настройках проекта, здесь он для узнавания */
.page__lead {
  color: var(--text-dim); font-size: var(--t-base); line-height: 1.5;
  margin: calc(-1 * var(--s-2)) 0 var(--s-4); max-width: 76ch;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* Состав команды проекта — раскрывается из стопки аватаров в шапке */
.teampop { min-width: 264px; padding: var(--s-2); }
.teampop__head {
  font-size: var(--t-2xs); font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-faint); padding: var(--s-2) var(--s-3) var(--s-2);
}
.teampop__row {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) var(--s-3); border-radius: var(--radius-sm);
}
.teampop__row:hover { background: var(--surface-2); }
.teampop__who { flex: 1; min-width: 0; font-size: var(--t-base); }
.teampop__who .note { font-size: var(--t-xs); }
.teampop__who > div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.teampop__x {
  flex: none; border: none; background: none; cursor: pointer; color: var(--text-faint);
  font-size: var(--t-sm); padding: var(--s-1); border-radius: var(--radius-sm); opacity: 0;
}
.teampop__row:hover .teampop__x { opacity: 1; }
.teampop__x:hover { color: var(--critical); background: var(--critical-soft); }
.teampop__add { margin-top: var(--s-1); border-top: 1px solid var(--border); border-radius: 0; }

/* ── Карточка проекта ────────────────────────────────────────────────────── */
/* Ряды одинаковой высоты: имя со статусом, две строки описания, полоса с
   числами, внизу лица команды. Пустое описание держит своё место — иначе
   соседние карточки разъезжаются и сетка перестаёт читаться рядами. */
/* Сетка проектов: четыре в ряд на большом мониторе, три на обычном, два на
   узком, один на телефоне. Раньше стояло auto-fill от 320px — и на широком
   экране получалось четыре узких столбца, в которых название проекта вроде
   «Реконструкция особняка на Пречистенке» переносилось трижды. Число колонок
   задано ступенями, а не подбором: так карточка на каждой ширине остаётся
   той величины, на которую рассчитано её содержимое. */
/* Три карточки в ряду, не четыре. На карточке теперь обложка слева от
   названия и ряд кнопок в подвале — в четверти ширины они не помещались
   и вставали в две строки каждый, отчего сетка теряла ряды. */
/* Число столбцов считает сама сетка, а не ступени по ширине экрана.
   Ступеней было три — 3, 2 и 1 столбец, — и между ними карточка оказывалась
   то просторной, то такой, что в неё не помещались ни название, ни кнопки
   разделов: «189 Скандинавия» читалось как «18 9…», а «Комплектация»
   обрезалась краем. Минимум в 340 точек — это ширина, на которой карточка
   ещё работает; сколько таких влезло, столько и столбцов. */
.projgrid {
  display: grid; gap: var(--s-4);
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
}
@media (max-width: 400px) { .projgrid { grid-template-columns: 1fr; } }
.projcard {
  display: grid; grid-template-rows: 1fr auto; gap: var(--s-3);
  text-align: left; font: inherit; cursor: pointer;
  /* Карточка лежит на полотне и обязана быть светлее его — как карточка
     задачи. См. --canvas / --raised в разделе тем. */
  background: var(--raised); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--s-5);
  transition: border-color var(--fast), box-shadow var(--fast), transform var(--fast);
}
.projcard:hover {
  border-color: var(--border-strong); box-shadow: var(--shadow-md);
}
/* Общий проект компании выделен рамкой акцентного цвета: он один такой и
   стоит первым, ошибиться с ним нельзя */
.projcard--company { border-color: var(--accent-soft); background: var(--accent-soft); }
.projcard--company:hover { border-color: var(--accent); }
/* Обложка слева, справа от неё столбцом имя и описание. Без переноса:
   описание должно стоять рядом с картинкой, а не под ней. */
.projcard__top { display: flex; align-items: flex-start; gap: var(--s-4); }
.projcard__head { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s-2); }
.projcard__line { display: flex; align-items: flex-start; gap: var(--s-2); flex-wrap: wrap; }
/* Обложка проекта: квадрат слева от названия, в высоту имени с описанием.
   Не круг — круг в приложении означает человека, и проект в кружке читался
   бы сотрудником. Скруглён так же, как карточка задачи.

   Размер — 88 точек: обложка в 44 была ровно значком, а по значку объект
   не узнать. Проект узнают по картинке быстрее, чем по названию: «та самая
   квартира с эркером» вспоминается раньше, чем «Мойка, 12». */
.projcard__cover {
  flex: none; width: 88px; height: 88px; border-radius: var(--radius);
  overflow: hidden; display: grid; place-items: center;
  background: var(--surface); color: var(--text-faint);
}
.projcard__cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.projcard__cover.is-empty { border: 1px solid var(--border); }
.projcard__cover svg { width: 60%; height: 60%; }
/* Та же обложка в настройках проекта — крупнее, чтобы было видно, что
   именно встанет на карточку после обрезки по центру. */
.projcard__cover--big { width: 96px; height: 96px; border-radius: var(--radius-lg); }
/* Название не ужимается ниже читаемого: рядом с ним в строке стоит плашка
   («Общий», «На паузе»), и с `min-width: 0` она забирала всё — от «189
   Скандинавия» оставалось «18 9…», а перенос по любому месту рвал число
   пополам. Теперь имя держит больше половины строки, а плашке, если ей
   не хватило, строка достаётся своя. */
.projcard__name {
  flex: 1 1 auto; min-width: 60%;
  font-size: var(--t-md); font-weight: 600; line-height: 1.3;
  color: var(--text); overflow-wrap: break-word;
  text-align: left; font-family: inherit; background: none; border: 0; padding: 0;
  cursor: pointer;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.projcard__top .chip { flex: none; }
.projcard__desc {
  color: var(--text-dim); font-size: var(--t-sm); line-height: 1.45; min-height: 2.9em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* Счёт задач — третьей строкой в том же столбце, что имя и описание,
   то есть справа от обложки. Своей строкой во всю ширину он оставлял
   слева пустой квадрат в размер обложки. */
.projcard__nums {
  display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap;
  font-size: var(--t-sm); color: var(--text-dim); margin-top: auto;
}
.projcard__late { color: var(--critical); font-weight: 600; }
/* Подвал переносится, а не вылезает. Лица, «Задачи» и «Комплектация»
   в одну строку на узкой карточке не помещались, и всё лишнее уезжало
   за её край — вместе с ним появлялась полоса прокрутки у всей страницы.
   Не влезло — кнопки уходят строкой ниже и остаются прижатыми вправо. */
.projcard__foot {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  border-top: 1px solid var(--border); padding-top: var(--s-3);
}
/* Лица не растягивают подвал и не наезжают на подпись: ряд занимает столько,
   сколько занимает, а лишнее уходит под обрез. Без overflow отрицательные
   поля аватарок выносили их поверх соседнего текста. */
.projcard__faces {
  display: flex; align-items: center; gap: 3px; flex: 1 1 auto;
  min-width: 0; overflow: hidden;
}
.projcard__faces .avatar { border: 2px solid var(--raised); margin-left: -6px; }
.projcard__faces .avatar:first-child { margin-left: 0; }
/* Кнопки разделов: с карточки чаще идут не «в проект вообще», а в задачи
   или в комплектацию. Прижаты вправо, лица занимают остальное. */
.projcard__go { flex: none; display: flex; gap: var(--s-2); margin-left: auto; }
@media (max-width: 760px) {
  /* Число колонок задано ступенями выше — здесь только то, что от них
     не зависит. Прежняя строка «в один столбец» ломала вторую ступень:
     между 640 и 760 должно оставаться два столбца. */
  /* В один столбец выравнивать нечего — пустое описание просто уходит */
  .projcard__desc { min-height: 0; }
  .projcard__desc:empty { display: none; }
  /* На телефоне обложка мельче: карточка здесь во всю ширину экрана,
     описание получает строку целиком, и картинка в 88 точек отодвигала бы
     его вниз, съедая половину видимой карточки. */
  .projcard__cover { width: 56px; height: 56px; }
  .projcard__top { gap: var(--s-3); }
  /* Поля в размер экрана: на телефоне карточка занимает всю ширину, и
     двадцатипиксельные поля с обеих сторон съедали десятую часть строки. */
  .projcard { padding: var(--s-4); gap: var(--s-2); }
}

/* Сводный график планирования: высота по числу строк, но не выше экрана */
.planchart {
  position: relative; height: clamp(320px, calc(100dvh - 320px), 900px);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); overflow: hidden;
}

/* ── Экран задачи ────────────────────────────────────────────────────────── */
/* Та же карточка, что и в панели сбоку, но на полотне страницы: своей прокрутки
   нет — листает страница, как и везде. Ширина ограничена: поля в строку на
   весь монитор читать невозможно. */
/* Ширины своей у страницы задачи больше нет — она такая же, как у главной
   и у списка проектов. Была уже: сперва 880, потом 1000 при общих 1440,
   и переход из списка в задачу выглядел как переход в другое приложение —
   полотно вдруг сужалось. Длинная строка в описании читается хуже узкой,
   это правда, но за неё платили тем, что на широком экране справа от задачи
   лежало пустое поле в треть монитора. */
.page--task { position: relative; }

/* Перенос файлов: цель — вся страница, а не кнопка «Добавить файл». Целиться
   в кнопку с файлом в руке неудобно, да и не всякий догадается, что можно.
   Страницу не заслоняем: рамка по краю и подсказка внизу экрана — содержимое
   должно остаться видно, чтобы было понятно, куда именно кладёшь. */
.taskdrop { display: none; }
.page--task.is-drop {
  outline: 2px dashed var(--accent); outline-offset: var(--s-3);
  border-radius: var(--radius-lg);
}
.page--task.is-drop .taskdrop {
  display: block; position: fixed; left: 50%; bottom: var(--s-6);
  transform: translateX(-50%); z-index: 60; pointer-events: none;
  background: var(--accent); color: var(--accent-text); font-weight: 600;
  font-size: var(--t-sm); padding: var(--s-2) var(--s-4);
  border-radius: var(--radius-full); box-shadow: var(--shadow-md);
}
.page--task .crumbs { margin-bottom: var(--s-2); }

/* Тело задачи одной колонкой: метки под названием, дальше то, что пишут и
   читают. Формы с двумя десятками полей здесь нет — всё, что видно на
   карточке, правится нажатием прямо в метке. */
.tbody { display: flex; flex-direction: column; gap: var(--s-3); }
.tbody__main {
  margin-top: var(--s-4); min-width: 0;
  display: flex; flex-direction: column; gap: var(--s-6);
}
.tbody__rest { margin-top: var(--s-5); }
.tbody__rest .progress-wrap .ckbar { flex: 1; margin: 0; }
.tbody__rest .tsec { margin-top: var(--s-5); }

/* Раздел: подпись мельче текста и волосяная линия под ней. Коробок нет —
   разделами командует воздух */
.tsec { min-width: 0; }
/* Раздел, которого пока нет: строка с плюсом вместо заголовка и пустого поля
   на пол-экрана. Рамка пунктиром — то же приглашение, что у заметок. */
.tsec--fold { margin-bottom: var(--s-3); }
.tsec__call {
  display: flex; align-items: center; gap: var(--s-2); width: 100%;
  padding: var(--s-3) var(--s-4);
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
  background: none; color: var(--text-faint); cursor: pointer;
  font: inherit; font-size: var(--t-sm); text-align: left;
  transition: color var(--fast), border-color var(--fast), background var(--fast);
}
.tsec__call:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.tsec__plus { font-size: var(--t-md); line-height: 1; }

/* Переход к соседней задаче: разбирая этап, после каждой возвращались
   в список. Стрелки стоят рядом с «…», где и остальное редкое. */
.taskstep { display: flex; gap: var(--s-1); flex: none; }
.taskstep button:disabled { opacity: .35; cursor: default; }

/* Поле, которое растёт по тексту: уголок растягивания предлагал работу,
   которой быть не должно — заметка сама знает, сколько ей места */
.textarea--grow { resize: none; overflow: hidden; }

.tsec__head {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint);
  padding-bottom: var(--s-2); margin-bottom: var(--s-3);
  border-bottom: 1px solid var(--border);
}
/* Поля на странице задачи — без рамок, пока их не трогают: страница должна
   читаться как записка, а не как анкета. Рамка появляется под курсором —
   этого хватает, чтобы понять, что текст правится. */
.tbody__main .textarea, .tbody__main .input {
  background: transparent; border-color: transparent;
}
.tbody__main .textarea:hover:not(:disabled),
.tbody__main .input:hover:not(:disabled) { border-color: var(--border); background: var(--surface); }
.tbody__main .textarea:focus, .tbody__main .input:focus {
  border-color: var(--accent); background: var(--surface);
}
/* Высоту теперь считает содержимое: коробка в сто шестнадцать пикселей
   стояла и под одной строкой, и под пустотой, читаясь как форма */
.textarea--lead { min-height: 0; }

/* ── Метки задачи ────────────────────────────────────────────────────────── */
/* Те же метки, что на карточке, только крупнее и с меню по нажатию. Первая
   строка — состояние: статус, сроки, кто делает. Вторая — где и что: проект,
   тип, этаж, помещение, приоритет, сложность. */
.tmeta { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
/* Люди — одной группой у правого края: по отдельности «+ Исполнитель»
   срывалась на пустую строку и читалась как обломок вёрстки */
.tmeta__who { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.tmeta > .spacer { flex: 1 1 var(--s-4); min-width: var(--s-4); }
.tmeta--tags { margin-top: var(--s-2); }
.tmeta__hot { font-size: var(--t-md); line-height: 1; }

.tpill {
  display: inline-flex; align-items: center; height: 30px; padding: 0 var(--s-3);
  border: 1px solid transparent; border-radius: var(--radius-full);
  background: var(--surface-2); color: var(--text-dim);
  font-size: var(--t-sm); font-weight: 500; white-space: nowrap;
  max-width: 260px; box-shadow: none;
}
.tpill__in { display: inline-flex; align-items: center; gap: var(--s-2); min-width: 0; }
.tpill__txt { overflow: hidden; text-overflow: ellipsis; display: inline-flex;
  align-items: center; gap: 5px; }
/* Огонёк важности на плашке задачи — тот же знак, что на карточке, только
   меньше: там он стоит в кружке отдельно, здесь идёт перед словом. */
.tpill__hot { font-size: var(--t-sm); line-height: 1; flex: none; }
/* Погасший: важность не поднята, но поднять её можно. Тот же приём, что
   у огонька на карточке, — знак остаётся на месте и не заводит новый ряд. */
.tpill__hot--off { filter: grayscale(1); opacity: .5; }
.tpill__dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--tpill-color, var(--text-faint));
}
.tpill--act { cursor: pointer; transition: background var(--fast), color var(--fast); }
.tpill--act:hover { background: var(--surface-3); color: var(--text); }
.tpill--act.is-open { background: var(--surface-3); color: var(--text); }
.tpill--act:active { transform: scale(.97); }

.tpill--status { font-weight: 600; color: var(--text); }
.tpill--project { background: var(--accent-soft); color: var(--accent-hover); font-weight: 600; }
.tpill--project.tpill--act:hover { background: var(--accent-soft); color: var(--accent); }
.tpill--floor { background: var(--surface-3); }
.tpill--type, .tpill--prio {
  background: color-mix(in srgb, var(--tpill-color, #64748b) 14%, transparent);
  color: var(--tpill-color, #64748b); font-weight: 600;
}
.tpill--type.tpill--act:hover, .tpill--prio.tpill--act:hover {
  background: color-mix(in srgb, var(--tpill-color, #64748b) 22%, transparent);
  color: var(--tpill-color, #64748b);
}
.tpill--date { background: transparent; border-color: var(--border); }
.tpill--date.tpill--act:hover { background: var(--surface-2); border-color: var(--border-strong); }
.tpill--late { color: var(--critical); border-color: var(--critical); font-weight: 600; }
.tpill--late.tpill--act:hover { background: var(--critical-soft); color: var(--critical); }
.tpill--who { padding-left: var(--s-1); }
.tpill--who .avatar { width: 22px; height: 22px; font-size: var(--t-2xs); }
/* Незаполненное — пунктиром: видно, что метка пустая и её можно поставить */
.tpill--put, .tpill--add, .tpill--quiet {
  background: transparent; border: 1px dashed var(--border-strong); color: var(--text-faint);
}
.tpill--put:hover, .tpill--add:hover, .tpill--quiet:hover {
  background: var(--surface-2); color: var(--text-dim); border-style: dashed;
}

/* ── Наш календарь вместо системного ─────────────────────────────────────── */
.datepop {
  position: fixed; z-index: 60; padding: var(--s-3);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
  animation: fade-in .1s var(--ease);
}
.datepop__head { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); }
.datepop__month {
  flex: 1; text-align: center; font-weight: 600; font-size: var(--t-sm);
}
.datepop__nav {
  width: 26px; height: 26px; border: none; background: transparent; cursor: pointer;
  border-radius: var(--radius-sm); color: var(--text-dim); font-size: var(--t-md); line-height: 1;
}
.datepop__nav:hover { background: var(--surface-2); color: var(--text); }
.datepop__grid { display: grid; grid-template-columns: repeat(7, 32px); gap: 2px; }
.datepop__wd {
  display: grid; place-items: center; height: 22px;
  font-size: var(--t-2xs); color: var(--text-faint); text-transform: uppercase;
}
.dateday {
  height: 30px; border: none; background: transparent; cursor: pointer;
  border-radius: var(--radius-sm); color: var(--text); font-size: var(--t-sm);
  font-variant-numeric: tabular-nums; transition: background var(--fast);
}
.dateday:hover { background: var(--surface-2); }
.dateday.is-out { color: var(--text-faint); opacity: .55; }
.dateday.is-off { color: var(--text-faint); background: var(--off); }
.dateday.is-off:hover { background: var(--surface-3); }
.dateday.is-today { box-shadow: inset 0 0 0 1.5px var(--today); font-weight: 600; }
.dateday.is-sel { background: var(--accent); color: var(--accent-text); font-weight: 600; }
.dateday.is-sel:hover { background: var(--accent-hover); }
.datepop__foot {
  display: flex; gap: var(--s-1); margin-top: var(--s-2);
  padding-top: var(--s-2); border-top: 1px solid var(--border);
}

@media (max-width: 760px) {
  .tpill { max-width: 100%; }
}

/* Название — заголовок страницы, а не поле формы: рамка появляется под
   курсором, чтобы было понятно, что его можно править */
/* Название забирает всю свободную ширину: раньше её пополам делил распорка
   перед кнопками, и на заголовок оставалась половина полосы — длинное имя
   ломалось на две строки на пустом месте. */
.taskhead { align-items: center; gap: var(--s-2); margin-bottom: var(--s-5); }
.taskhead > .spacer { flex: 0 0 var(--s-3); }
.taskhead .input {
  flex: 1; min-width: 0; height: auto; padding: var(--s-1) var(--s-2);
  font-size: var(--t-2xl); font-weight: 600; line-height: 1.2;
  border-color: transparent; background: transparent;
  resize: none; overflow: hidden; font-family: inherit;
}
.taskhead .input:hover:not(:disabled) { border-color: var(--border); background: var(--surface); }
.taskhead .input:focus { border-color: var(--accent); background: var(--surface); }
@media (max-width: 760px) {
  .taskhead .input { font-size: var(--t-xl); }
  /* Название занимает свою строку целиком, а стрелки «предыдущая-следующая»
     и «…» встают под ним справа.

     В одну строку на телефоне помещалось 154 пикселя из 366: остальное
     забирали стрелка назад, две круглые стрелки и многоточие. «Обмерный
     план» — два коротких слова — ломался на две строки при живом свободном
     месте справа. Две строки заголовок занимал в любом случае; теперь на
     второй стоят органы, а не перенос. */
  .taskhead { flex-wrap: wrap; row-gap: var(--s-2); }
  .taskhead .input { flex: 1 0 calc(100% - 38px); }
  .taskhead > .spacer { flex: 1 1 auto; order: 1; }
  .taskhead .taskstep, .taskhead .roundbtn { order: 2; }
}

/* ── Карточка коллеги в разделе «Команда» ────────────────────────────────── */
/* Человек, почта и одна строка состояния: на месте он сегодня или нет.
   Ничего больше здесь не нужно — за задачами идут в проект, за отпуском в табель. */
.pcard {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--s-4);
  display: flex; flex-direction: column; gap: var(--s-3); align-items: flex-start;
}
.pcard__top { display: flex; align-items: center; gap: var(--s-4); min-width: 0; }
/* «в отпуске до 28.08.2026» — плашка длинная, и в узкой карточке (четыре
   в ряд) она распирала верх наружу вместе с именем. Пусть переносится. */
.pcard__top .chip { display: inline-flex; white-space: normal; }
.pcard__name { font-weight: 600; font-size: var(--t-base); }
.pcard__mail {
  display: block; font-size: var(--t-sm); color: var(--text-dim);
  text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Заголовок колонки — кнопка: по нему открывается полный список проектов */
.side__title--act {
  background: none; border: none; cursor: pointer; width: 100%; text-align: left;
  font: inherit; font-size: var(--t-2xs); font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-faint); padding: var(--s-2) 0;
  transition: color var(--fast);
}
.side__title--act:hover { color: var(--text-dim); }

/* Значки приложения: свой контур вместо эмодзи. Эмодзи на маке выглядят
   прилично, а на windows и android — как попало, разного веса и размера,
   и в одном ряду не стоят. */
.ic { display: inline-flex; align-items: center; justify-content: center; flex: none; }
.ic svg { display: block; }
.navitem__icon .ic svg { width: 18px; height: 18px; }

/* Наведение не двигает элемент. Прыгающая под курсором карточка сбивает
   прицел: пока рука ведёт мышь к кнопке, кнопка уезжает. Отклик даём
   рамкой и тенью — они заметны и ничего не сдвигают. Нажатие сдвигать
   можно: там палец уже на месте. */

/* Плашка песочницы: спокойная, но заметная. Человек по ссылке должен сразу
   понять, что всё здесь ненастоящее и сломать он ничего не может. */
.sandbox {
  display: inline-flex; align-items: center; gap: var(--s-2); flex: none;
  height: 32px; padding: 0 var(--s-2) 0 var(--s-3); border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--warn, #e0932a) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn, #e0932a) 35%, transparent);
  font-size: var(--t-2xs); color: var(--text);
}
.sandbox__dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--warn, #e0932a);
}
.sandbox__text { display: flex; gap: var(--s-2); align-items: baseline; white-space: nowrap; }
.sandbox__text span { color: var(--text-dim); }
.sandbox__reset {
  border: none; background: none; cursor: pointer; font: inherit; font-weight: 600;
  color: var(--accent); padding: 0 var(--s-2); border-radius: var(--radius-sm);
}
.sandbox__reset:hover { background: var(--surface-2); }
/* На узком экране остаётся только слово и кнопка: объяснение вытесняло
   разделы из полосы */
@media (max-width: 1400px) { .sandbox__text span { display: none; } }
@media (max-width: 900px) { .sandbox__text b { display: none; } }

/* Скрытый текст — как в телеграме: текст размыт и присыпан мелкими точками,
   которые медленно плывут. Чёрный прямоугольник кричал «здесь что-то есть»
   и выглядел дырой в тексте; размытое пятно читается как текст, который
   просто не разобрать. Защиты тут нет — строка лежит в базе как есть; это
   про то, чтобы пароль от общего сервиса не читался через плечо. */
.spoiler {
  cursor: pointer; user-select: none; border-radius: var(--radius-sm);
  padding: 0 1px;
  /* Сам текст невидим, но оставляет размытый след — форма слов сохраняется */
  color: transparent;
  text-shadow: 0 0 5px var(--text);
  background-image:
    radial-gradient(circle at 1px 1px, currentColor .8px, transparent .9px),
    radial-gradient(circle at 3px 4px, currentColor .6px, transparent .7px);
  background-size: 6px 6px, 8px 7px;
  background-position: 0 0, 2px 1px;
  animation: spoiler-drift 9s linear infinite;
  transition: text-shadow var(--fast), color var(--fast);
}
.spoiler * { color: inherit; text-shadow: inherit; }
@keyframes spoiler-drift {
  to { background-position: 24px 12px, 26px 15px; }
}
.spoiler.is-open {
  color: inherit; text-shadow: none; background-image: none;
  animation: none; cursor: auto; user-select: text;
  background-color: var(--surface-2);
}
.spoiler.is-open * { color: inherit; text-shadow: none; }
@media (prefers-reduced-motion: reduce) { .spoiler { animation: none; } }

/* Ширину страница задачи берёт общую — см. .page--task выше. */
/* Подпись перед исполнителями: без неё аватарки читались как «кто-то» */
.tpill__lead {
  font-size: var(--t-2xs); color: var(--text-faint);
  text-transform: uppercase; letter-spacing: .08em; margin-left: var(--s-2);
}
/* Предлог перед датой — та же подпись, что «Поставил» и «Делает», но вплотную
   к своей кнопке: «с» и дата читаются вместе, а с общим отступом слева предлог
   отрывался от даты и прилипал к предыдущей плашке */
.tpill__lead--pre { margin-left: 0; margin-right: calc(-1 * var(--s-1)); }
/* Подпись со своим значением. Переносится парой: по отдельности на телефоне
   выходило «ПРОСРОЧЕНА С» в конце строки и «3 августа» в начале следующей —
   подпись без числа и число без подписи. Внутри пары переносов нет,
   между парами есть. */
.tmeta__pair { display: inline-flex; align-items: center; gap: var(--s-2); }
.tmeta__pair .tpill__lead { margin-left: 0; }
/* Длинное имя задачи не должно распирать меню на весь экран */
.menupop { max-width: min(420px, calc(100vw - 32px)); }
.menurow { text-align: left; overflow-wrap: anywhere; }

.pcard__mail:hover { color: var(--accent); text-decoration: underline; }
/* Карточка человека отвечает на вопрос «к кому идти»: должность, город,
   чем поможет и как связаться. Строки набраны парами «что — значение»,
   чтобы карточка читалась сверху вниз, а не расползалась абзацем. */
/* Карточек в ряд — сколько задано в админке, но не в ущерб читаемости:
   ширина колонки считается точно под нужное число, а если она уходит ниже
   210 пикселей, браузер сам ставит в ряд меньше. Так одно и то же правило
   даёт «четыре в ряд» на большом экране и одну на телефоне. */
/* Отбор людей по имени и умению */
.teamfind { max-width: 360px; margin-bottom: var(--s-3); }
.softfilter { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-bottom: var(--s-4); }
.softtag {
  border: 1px solid var(--border); background: var(--surface); cursor: pointer;
  font: inherit; font-size: var(--t-xs); color: var(--text-dim);
  padding: var(--s-1) var(--s-3); border-radius: var(--radius-full);
  transition: border-color var(--fast), color var(--fast), background var(--fast);
}
.softtag:hover { border-color: var(--border-strong); color: var(--text); }
.softtag.is-active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }
.softtag--flat { cursor: default; background: var(--surface-2); border-color: transparent; }

/* Короткая карточка: одной высоты со всеми, остальное открывается окном */
.pcard--short {
  text-align: left; font: inherit; color: inherit; cursor: pointer;
  gap: var(--s-3);
}
.pcard--short:hover { border-color: var(--border-strong); }
.pcard__state { display: flex; }

.cardsgrid--people {
  align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(
    min(100%, max(210px, (100% - (var(--team-cols, 3) - 1) * var(--s-3)) / var(--team-cols, 3))),
    1fr));
}
.pcard { position: relative; }
/* Карандаш в углу, как у заметок: кнопка в потоке карточки ломала её строй */
.pcard__act {
  position: absolute; top: var(--s-2); right: var(--s-2);
  border: none; background: none; cursor: pointer; color: var(--text-faint);
  width: 26px; height: 26px; border-radius: var(--radius-sm); padding: 0;
  font-size: var(--t-base); opacity: 0;
  transition: opacity var(--fast), background var(--fast), color var(--fast);
}
/* Виден всегда: на телефоне наведения нет, и правка своей карточки
   становилась тайной */
.pcard__act { opacity: .45; }
.pcard:hover .pcard__act, .pcard:focus-within .pcard__act { opacity: .8; }
.pcard__act:hover { opacity: 1; color: var(--accent); background: var(--surface-2); }
@media (hover: none) { .pcard__act { opacity: .5; } }
.pcard__post { font-size: var(--t-sm); color: var(--accent); font-weight: 500; }
.pcard__about { font-size: var(--t-sm); color: var(--text-dim); line-height: 1.45; }
.pcard__lines { display: flex; flex-direction: column; gap: var(--s-1); width: 100%; }
.pcard__line { display: flex; gap: var(--s-2); align-items: baseline; font-size: var(--t-sm); }
.pcard__k {
  flex: none; min-width: 62px; font-size: var(--t-2xs); color: var(--text-faint);
  text-transform: uppercase; letter-spacing: .08em;
}
.pcard__v { color: var(--text); text-decoration: none; word-break: break-word; min-width: 0; }
a.pcard__v:hover { color: var(--accent); text-decoration: underline; }
.pcard__soft { font-size: var(--t-sm); color: var(--text-dim); white-space: pre-line; }
/* Город под должностью: раньше он жил внутри плашки «на месте · Москва»,
   и чтобы узнать, где человек, приходилось читать очевидное «на месте» */
.pcard__where { font-size: var(--t-xs); color: var(--text-faint); margin-top: var(--s-05); }
/* «Чем помогает» — в две строки: у одного строка, у другого пять, и ряд
   карточек выходил рваным */
.pcard__helps {
  font-size: var(--t-sm); color: var(--text-dim); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.pcard__hobby { font-size: var(--t-sm); color: var(--text-faint); font-style: italic; }

/* Дни рождения: список, который никто не ведёт руками — он собирается
   из карточек и сам сдвигается по ходу года */
/* Дни рождения — полоса на год вперёд. Аватарок нет: они уже есть
   на карточках выше, а здесь важна дата, и ряд лиц перетягивал внимание. */
/* Подписи прижимаются к краям расчётом, но первый кадр рисуется до него —
   и без обрезки страница успевала обзавестись прокруткой вбок */
.bline { position: relative; padding-top: var(--s-5); overflow: hidden; }
.bline__axis {
  position: relative; height: 1px; background: var(--border);
}
.bline__tick {
  position: absolute; top: calc(-1 * var(--s-4)); transform: translateX(-50%);
  font-size: var(--t-2xs); color: var(--text-faint); white-space: nowrap;
}
.bline__tick::after {
  content: ''; position: absolute; left: 50%; top: 100%;
  width: 1px; height: var(--s-2); background: var(--border);
}
/* Сегодня — начало отсчёта, поэтому всегда у левого края. Чертой, а не
   точкой: точка читалась бы как ещё чей-то день рождения */
.bline__now {
  position: absolute; left: 0; top: -6px; width: 1px; height: 13px;
  background: var(--today);
}

.bline__track { position: relative; transition: height var(--fast); }
.bline__pin {
  position: absolute; top: 0; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center;
}
/* Точка сидит на оси, ножка тянется от неё вниз до подписи: без ножки
   непонятно, какому имени принадлежит какая дата */
.bline__dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--text-faint); margin-top: -4px;
}
.bline__stem {
  width: 1px; background: var(--border);
  height: calc(var(--lane, 0) * 34px + var(--s-2));
}
.bline__label {
  display: flex; flex-direction: column; align-items: center;
  white-space: nowrap; line-height: 1.25;
  transform: translateX(var(--shift, 0px));
}
.bline__name { font-size: var(--t-sm); font-weight: 600; }
.bline__date { font-size: var(--t-2xs); color: var(--text-faint); font-family: var(--mono); }

.bline__pin.is-soon .bline__dot { background: var(--accent); }
.bline__pin.is-soon .bline__date { color: var(--accent); }
.bline__pin.is-today .bline__dot { background: var(--today); width: 9px; height: 9px; }
.bline__pin.is-today .bline__name { color: var(--today); }
.bline__pin.is-today .bline__date { color: var(--today); font-weight: 600; }

/* Узко — год в строку не ложится, и полоса становится обычным списком.
   Прокрутки вбок посреди страницы, которая листается вниз, не ждёшь. */
.bline--list { padding-top: 0; overflow: visible; }
.bline--list .bline__axis,
.bline--list .bline__dot,
.bline--list .bline__stem { display: none; }
.bline--list .bline__track { display: flex; flex-direction: column; }
.bline--list .bline__pin {
  position: static; transform: none;
  flex-direction: row; align-items: baseline;
  padding: var(--s-2) 0; border-bottom: 1px solid var(--border);
}
.bline--list .bline__pin:last-child { border-bottom: none; }
.bline--list .bline__label {
  flex-direction: row; align-items: baseline; gap: var(--s-3);
  transform: none; white-space: normal; width: 100%;
}
/* Дата первой: по ней и ищут, а имена от этого встают в общую колонку */
.bline--list .bline__date { order: -1; min-width: 88px; flex: none; }

/* Включённый фильтр видно по акценту: иначе непонятно, почему задач мало */
.select.is-on {
  border-color: var(--accent); color: var(--accent); font-weight: 600;
  background-color: var(--accent-soft);
}
/* То же самое для кнопки-воронки: значок без подписи обязан показывать,
   что отбор включён, иначе половина задач пропадает без объяснения */
.btn.is-on {
  border-color: var(--accent); color: var(--accent);
  background-color: var(--accent-soft); position: relative;
}
.btn.is-on::after {
  content: ''; position: absolute; top: 3px; right: 3px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 2px var(--surface);
}

/* ── Телефон: переосмысление ─────────────────────────────────────────────────
   Раздел держим последним: он правит то, что объявлено выше для широкого
   экрана. Правил здесь три.

   Первое: листает сама страница, а не рамка внутри неё. Приложение было
   закреплено по краям окна (body: position fixed), и содержимое ездило внутри
   своей коробки. На телефоне это стоит дорого: браузер не прячет адресную
   строку, потому что страница «не листается», прокрутка теряет инерцию,
   а вложенные полосы спорят друг с другом.

   Второе: палец не мышь. Карточка берётся долгим нажатием (это в скрипте),
   всё нажимаемое — не меньше 44 пикселей, наведения нет вовсе.

   Третье: на 375 точках дорога каждая строка. Карточки не держат общую
   высоту, вид «строками» становится настоящим списком в одну линию. */

@media (max-width: 760px) {
  html, body, #root { height: auto; }
  body { position: static; inset: auto; overflow-x: hidden; overscroll-behavior: auto; }
  .app { height: auto; min-height: 100dvh; overflow: visible; padding-bottom: 0; }
  .shell { display: block; flex: none; min-height: 0; }
  .main {
    overflow: visible; min-height: 0; overscroll-behavior: auto;
    margin: 0; border: none; border-radius: 0;
  }
  /* Шапка и разделы остаются на месте: до поиска и до «Проектов» рука
     дотягивается на любом месте списка */
  .topbar {
    position: sticky; top: 0; z-index: 30; background: var(--bg);
    padding-left: max(var(--s-3), env(safe-area-inset-left));
    padding-right: max(var(--s-3), env(safe-area-inset-right));
  }
  .nav {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    padding-left: max(var(--s-1), env(safe-area-inset-left));
    padding-right: max(var(--s-1), env(safe-area-inset-right));
    box-shadow: 0 -1px 0 var(--border);
  }
  /* Под полосой разделов должно оставаться место — иначе последняя задача
     прячется под неё, и до неё не долистать. Высота полосы взята из её же
     объявления, а не переписана числом: расходились они молча. Сверх неё —
     ещё полполосы воздуха, чтобы последняя строка не липла к вкладкам. */
  .page {
    padding-left: max(var(--s-3), env(safe-area-inset-left));
    padding-right: max(var(--s-3), env(safe-area-inset-right));
    padding-bottom: calc(var(--nav-h, 82px) + var(--s-4) + env(safe-area-inset-bottom));
  }

  /* Карточка по содержимому, а не по общей высоте: ровный низ нужен сетке
     в три столбца, а в один столбец он оборачивался пустотой в пол-экрана */
  .cardsgrid--home .kcard--free { min-height: 0; }
  .kcard--free { gap: var(--s-1); }
  .kcard--free .kcard__meta { padding-top: var(--s-1); }

  /* «Строками» — тот же список, что на большом экране: название во всю
     ширину, под ним метки, а срок и лицо у правого края второй строки.

     Раскладка взята с компьютера намеренно, а не сведена к телефонной:
     один и тот же список не должен на разных экранах отвечать на разные
     вопросы. Метки — проект, этаж, помещение, тип — это и есть ответ
     на «что за задача»; без них три «Обмерных плана» из трёх проектов
     выглядят одинаково.

     Разница с компьютером только в ширине, и она разрешается сама:
     ряд меток переносится и обрезается по высоте одной строки — лишнее
     уходит за край по одной метке, начиная с наименее важной. Проект
     не сжимается никогда, он объявлен нерастяжимым выше по файлу.

     Срок при этом остаётся короткой записью («03.08», не «до 3 августа»):
     на второй строке он делит место с метками, и полная подпись съедала бы
     их все. */
  .cardsgrid--home.is-rows { gap: var(--s-1); }
  .is-rows .kcard--free {
    display: grid; align-items: center;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: 'title title' 'tags meta';
    column-gap: var(--s-2); row-gap: var(--s-1);
    min-height: 64px; padding: var(--s-2) var(--s-3);
  }
  .is-rows .kcard__title { grid-area: title; min-width: 0; }
  .is-rows .kcard__stage { display: none; }
  /* Ровно одна строка меток, и обрез по ней точный.

     Высота плашки задана числом, а высота ряда равна ей: иначе от второй,
     перенесённой строки оставалась снизу видимая полоска — серые огрызки
     под метками, которые читались как поломка вёрстки. Пока высота плашки
     складывалась из кегля и полей, совпадение этих двух чисел было делом
     случая. */
  .is-rows .kcard__tags {
    grid-area: tags; display: flex; margin: 0; gap: var(--s-1);
    flex-wrap: wrap; overflow: hidden; max-height: 22px; min-width: 0;
  }
  /* Кегль и поля у ярлыка общие — они заданы в `.ktag` один раз на всё
     приложение. Здесь только высота: ряд обрезается ровно по ней, и число
     должно совпасть с настоящей высотой плашки. */
  .is-rows .kcard__tags > * {
    max-width: 100%; height: 22px; display: inline-flex; align-items: center;
  }
  /* «+ Этаж», «+ Помещение», «+ Тип» — приглашения заполнить, а не сведения
     о задаче. В строке на телефоне метки не нажимаются (нажимается сама
     строка), так что приглашение никуда не ведёт, а место у настоящих
     меток отнимает. На карточках и на большом экране остаются. */
  .is-rows .kcard__tags .ktag--pusto { display: none; }
  .is-rows .kcard__meta {
    grid-area: meta; margin: 0; padding: 0; gap: var(--s-2);
    justify-content: flex-end; flex-wrap: nowrap;
  }
  /* Срок покороче: полная подпись съедала половину строки, и от названия
     оставалось восемь букв — три задачи подряд читались одинаково */
  .is-rows .kcard__date-long { display: none; }
  .is-rows .kcard__date-brief { display: inline; }
  .is-rows .kcard__pct { display: none; }
  .is-rows .kcard--add { min-height: 52px; }

  /* Заголовок раздела и его кнопки — одной строкой */
  .hblock__head { gap: var(--s-1); margin-bottom: var(--s-3); }
  /* Три заголовка на главной — «Мои задачи», «Поручил», «Наблюдаю» —
     набраны одинаково. На большом экране «Мои задачи» крупнее: своя работа
     главная, чужая стоит под ней. На телефоне эта разница не работает:
     заголовки идут не рядом, а один под другим через экран прокрутки,
     сравнить их не с чем, и разный кегль читается не как старшинство,
     а как разнобой.
     Вес селектора поднят нарочно: правило `.fold--home > summary` объявлено
     ниже по файлу, и при равной силе побеждало бы оно — кегль задавался бы
     тем, что стоит дальше, а не тем, что относится к телефону. */
  .hblock__head h2,
  .fold.fold--home > summary { font-size: var(--t-base); }
  /* Между разделами — s-3 вместо s-5. На большом экране 24 пикселя отделяют
     раздел от раздела; на телефоне, где всё и так идёт в один столбец,
     они превращают одну страницу в три, между которыми надо ехать. */
  .fold--home { margin-top: var(--s-3); }
  /* От счёта задач остаётся одно число: «Мои задачи 9», «Поручил 12».

     «9 всего · 6 просрочено» — это полстроки на экране в 390 точек, а под
     ним сразу идёт список, где просроченное и так обведено красным. Число
     же нужно: у свёрнутого «Поручил» оно единственное, что говорит, есть
     ли там что-нибудь.

     Слово «всего» и просрочка убираются видом, а не разметкой: при повороте
     телефона строка должна меняться сама, а не ждать перерисовки списка.

     Число остаётся в одной строке с заголовком — на отдельную его выносить
     незачем, оно короткое.

     «Плюс» уходит к правому краю. На телефоне у него нет подписи — это
     просто синий квадрат, — и вплотную к заголовку он читался частью
     заголовка. У края он читается кнопкой раздела, как в любом приложении
     на этом же телефоне. */
  .hblock__count-w, .hblock__count .is-overdue { display: none; }
  .hblock__count { gap: 0; }
  /* Кнопка встаёт за распоркой по порядку, а не по автоматическому полю.
     `margin-left: auto` здесь не работает: свободное место в гибкой строке
     сперва раздаётся растущим элементам и только потом автополям, а распорка
     растущая — она забирала всё, и поле вычислялось в нуль. Распорку при
     этом трогать нельзя: в других разделах шапки она разводит заголовок
     и органы, у которых своей кнопки нет. */
  .hblock__head > .btn--primary { order: 3; }
  .hblock__head > .hblock__tools { order: 4; }
  /* То же и в свёрнутых разделах: у «Поручил» и «Наблюдаю» заголовок — это
     summary, а не .hblock__head, и правило выше их не касалось. Кнопка
     оставалась там, где стояла, — вплотную к числу, и строка читалась
     как «Поручил 11+ Задача». */
  .fold--home > summary > .btn--primary { order: 3; }
  .fold--home > summary > .hblock__tools,
  .fold--home > summary > span:last-child { order: 4; }

  /* Плитки обзора демо — в два столбца, а не потоком. Потоком они вставали
     как придётся: одна на строке, вторая на строке, две в ряд — четыре
     плитки одного веса выглядели обломками разной длины. */

  /* Окно вылезает снизу листом, а не висит посреди экрана: до кнопок ближе,
     на клавиатуре не подпрыгивает */
  .modal-backdrop { place-items: end center; padding: 0; }
  .modal {
    width: 100%; max-width: 100%; border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    max-height: 92dvh; animation: slide-up .18s var(--ease);
  }
  .modal__foot { padding-bottom: calc(var(--s-4) + env(safe-area-inset-bottom)); }
  .modal__foot .btn { flex: 1; min-height: 44px; }

  /* Правила, прятавшего ряд разрезов канбана, здесь больше нет: разрез
     переехал в меню «…» на любой ширине, и прятать стало нечего. */

  /* Метки задачи и кнопки в меню — под палец */
  .tpill { height: 34px; }
  .menurow { min-height: 44px; }
  .roundbtn { width: 42px; height: 42px; }
}

/* Карточка, поднятая долгим нажатием: видно, что она «взята» и следующее
   движение пальца потащит её, а не пролистает список */
/* Закрытая задача уходит из списка не рывком: успевает быть увиденной */
.kcard--free.is-leaving {
  opacity: 0; transform: scale(.97);
  transition: opacity .2s var(--ease), transform .2s var(--ease);
  pointer-events: none;
}

.kcard.is-lifted, .kcard--free.is-lifted {
  transform: scale(1.03); box-shadow: var(--shadow-md);
  transition: transform .12s var(--ease), box-shadow .12s var(--ease);
}

/* ── Дедлайны ────────────────────────────────────────────────────────────────
   Даты, к которым что-то сдаётся: согласование, выезд, отправка в печать.
   Раньше они лежали карточками в колонках канбана и назывались вехами —
   слово из графиков работ, которое человеку со стороны не говорит ничего,
   а карточка среди задач выглядела задачей, которую нельзя взять в работу.
   Теперь это отдельная полоса под задачами; на диаграмме они и так видны
   ромбами на своей линии, там полосы нет. */
/* Всё в одну строку: заголовок, сами даты и «+». Дедлайнов на проект
   немного — три-четыре, — и отдельная строка под заголовок «ДЕДЛАЙНЫ»
   стоила больше места, чем сами дедлайны под ней. */
.duebar {
  padding: var(--s-4) var(--s-4) var(--s-2);
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
}
/* Комплектация свою полосу дедлайнов строит сама и лежит вне .projboard —
   поля ей нужны ровно те же, что задаёт `.projboard .duebar`, иначе при
   переходе «Задачи → График закупок» полоса, панель и всё под ними съезжают
   на восемь точек вниз. Стоять правило должно ниже базового: вес у них
   одинаковый, и решает порядок. */
.duebar--flush { padding: var(--s-2) 0; }
.duebar__head {
  display: flex; align-items: center; gap: var(--s-2); flex: none;
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint);
}
.duebar__n {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: 0; opacity: .8;
}
.duebar__row {
  display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center;
  /* Основа 0, а не auto: с auto ряд заявлял ширину своего содержимого,
     не влезал рядом с заголовком и целиком уезжал на вторую строку —
     ровно то, от чего мы уходили. Переносятся пусть сами даты внутри. */
  flex: 1 1 0; min-width: 0;
}
/* Полоса дедлайнов — одна строка, и место в ней делится между тремя-четырьмя
   датами. Ширина ограничена жёстче прежнего: длинное имя уходит под обрез,
   целиком оно есть во всплывающей и в самой задаче. */
.duechip {
  display: inline-flex; align-items: center; gap: var(--s-2); max-width: 260px;
  height: 32px; padding: 0 var(--s-3); border-radius: var(--radius-full);
  border: 1px solid var(--border); background: var(--raised);
  font: inherit; font-size: var(--t-sm); color: var(--text); cursor: pointer;
  transition: border-color var(--fast), box-shadow var(--fast);
}
.duechip:hover { border-color: var(--border-strong); box-shadow: var(--shadow-xs); }
.duechip__date {
  font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text-dim); flex: none;
}
.duechip__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Прошедший срок — тем же красным, что и просроченная задача */
.duechip.is-late { border-color: var(--critical); background: var(--critical-soft); }
.duechip.is-late .duechip__date { color: var(--critical); }
/* «Добавить дедлайн» — один знак в той же круглой рамке, что у дедлайнов
   рядом: круг, а не пилюля, потому что внутри одна буква и растягивать её
   не во что. Пунктир говорит «здесь пока пусто», как у пустой карточки
   задачи, — форма общая на всё приложение. */
.duechip--add {
  border-style: dashed; color: var(--text-faint);
  width: 32px; padding: 0; justify-content: center; flex: none;
  font-size: var(--t-md); line-height: 1;
}
.duechip--add:hover { color: var(--accent); border-color: var(--accent); }

/* ── Сводка «Что смотрят» в админке ───────────────────────────────────────
   Полоски, а не цифры столбцом: у списка из сорока строк вопрос не «сколько
   именно», а «что заметно больше остального», и на это отвечает длина,
   а не число. Число стоит рядом для тех случаев, когда всё-таки нужно точно. */
.statlist { display: flex; flex-direction: column; gap: var(--s-1); }
.statrow {
  display: grid; grid-template-columns: minmax(0, 1fr) 3fr 56px 40px;
  align-items: center; gap: var(--s-3); font-size: var(--t-sm);
}
.statrow__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.statrow__bar { height: 8px; border-radius: var(--radius-full); background: var(--well); }
.statrow__fill {
  display: block; height: 100%; border-radius: var(--radius-full);
  background: var(--accent); min-width: 2px;
}
.statrow__n, .statrow__people {
  font-variant-numeric: tabular-nums; text-align: right; color: var(--text-dim);
}
/* Сколько человек — тише числа нажатий: это уточнение, а не главное */
.statrow__people { color: var(--text-faint); font-size: var(--t-xs); }
/* Тема, которую сейчас не носит никто: ради этой строки раздел и заводили */
.table tr.is-cold td { color: var(--text-faint); }
.table tr.is-cold td:first-child { font-style: italic; }

/* ── Выбор оформления ─────────────────────────────────────────────────────
   Два вопроса подряд: когда какая тема и какие именно. Режимы — плитки,
   темы — образцы: то же приложение в миниатюре, фон, полотно, пилюля
   раздела, строки текста.

   Внутри образца стоит своя `data-theme`, поэтому картинка красится сама:
   ни одного цвета числом, добавили тему — образец появился вместе с ней.
   Штрихи внутри картинки заданы пикселями — это рисунок, а не вёрстка,
   и на общую сетку отступов он не встаёт. */

.modes { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-2); }
@media (max-width: 460px) { .modes { grid-template-columns: 1fr; } }
.mode {
  display: flex; flex-direction: column; gap: var(--s-05); text-align: left;
  padding: var(--s-3); border-radius: var(--radius); cursor: pointer;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  transition: border-color var(--fast), background var(--fast);
}
.mode:hover { border-color: var(--border-strong); }
.mode.is-active { border-color: var(--accent); background: var(--accent-soft); }
.mode__name { font-weight: 600; }
.mode.is-active .mode__name { color: var(--accent); }
.mode__note { font-size: var(--t-xs); color: var(--text-dim); line-height: 1.4; }

.thms { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--s-2); }
.thm {
  display: flex; flex-direction: column; gap: var(--s-1); text-align: left;
  padding: var(--s-1); border-radius: var(--radius-lg); cursor: pointer;
  border: 1px solid transparent; background: none; color: var(--text);
  transition: border-color var(--fast), box-shadow var(--fast);
}
.thm:hover { border-color: var(--border-strong); }
/* Обводка выбранного рисуется на кнопке, а не на картинке: на картинке лежит
   чужая тема, и обводка взяла бы её акцент вместо нашего */
.thm.is-active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.thm__name { font-size: var(--t-sm); font-weight: 600; padding: 0 var(--s-1); }
.thm.is-active .thm__name { color: var(--accent); }
.thm__note {
  font-size: var(--t-xs); color: var(--text-dim); line-height: 1.35;
  padding: 0 var(--s-1) var(--s-1);
}

.thm__pic {
  display: flex; gap: 4px; padding: 6px; aspect-ratio: 16 / 10; overflow: hidden;
  border-radius: var(--radius); background: var(--bg); border: 1px solid var(--border);
}
.thm__nav { flex: 0 0 28%; display: flex; flex-direction: column; gap: 4px; }
.thm__pill { height: 8px; border-radius: var(--radius-full); background: var(--surface); box-shadow: var(--shadow-xs); }
.thm__item { height: 5px; border-radius: var(--radius-full); background: var(--surface-3); }
.thm__page {
  flex: 1; display: flex; flex-direction: column; gap: 5px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.thm__bar { height: 7px; width: 58%; border-radius: var(--radius-full); background: var(--accent); }
.thm__line { height: 4px; border-radius: var(--radius-full); background: var(--text-dim); opacity: .5; }
.thm__line--short { width: 62%; }
/* ── База знаний ─────────────────────────────────────────────────────────────
   Две колонки: слева дерево страниц, справа сам документ. Дерево — не вторая
   навигация, а оглавление одной книги, поэтому оно без заливки и рамки, как
   и остальные списки в приложении.

   Документ намеренно уже полотна (--kb-line): инструкцию читают глазами,
   а строка в тысячу точек шириной читается плохо. Абзац начинается там же,
   где заголовок, — никаких отступов слева, кроме тех, что человек сделал
   сам клавишей Tab. */
.kbpage { padding-top: var(--s-4); }
.kb { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: var(--s-6); }
.kbmain { min-width: 0; }
.kbdoc { max-width: var(--kb-line, 760px); }

/* Дерево слева */
.kbside { min-width: 0; position: sticky; top: var(--s-2); align-self: start; }
.kbside__top { display: flex; gap: var(--s-2); align-items: center; margin-bottom: var(--s-3); }
.kbside__search {
  flex: 1; min-width: 0; height: 32px; padding: 0 var(--s-3);
  border: 1px solid var(--border); border-radius: var(--radius-full);
  background: var(--surface-2); color: var(--text);
  font: inherit; font-size: var(--t-sm);
}
.kbside__search:focus { outline: none; border-color: var(--accent); background: var(--surface); }
.kbside__new {
  flex: none; width: 32px; height: 32px; border-radius: var(--radius-full);
  border: 1px solid var(--border); background: var(--surface); color: var(--text-dim);
  font: inherit; font-size: var(--t-md); line-height: 1; cursor: pointer;
  transition: color var(--fast), border-color var(--fast);
}
.kbside__new:hover { color: var(--accent); border-color: var(--accent); }
.kbside__tree { display: flex; flex-direction: column; gap: 1px; }

.kbrow {
  display: flex; align-items: center; gap: var(--s-1);
  padding: var(--s-1) var(--s-2) var(--s-1) calc(var(--s-1) + var(--depth, 0) * var(--s-4));
  border-radius: var(--radius); border: 1px solid transparent; background: none;
  color: var(--text-dim); font: inherit; font-size: var(--t-sm); text-align: left;
  cursor: pointer; user-select: none;
  transition: background var(--fast), color var(--fast);
}
.kbrow:hover { background: var(--surface-2); color: var(--text); }
.kbrow.is-active { background: var(--accent-soft); color: var(--text); font-weight: 600; }
.kbrow.is-dragging { opacity: .4; }
/* Куда упадёт перетаскиваемая страница: внутрь — подсветкой, рядом — чертой */
.kbrow.is-over { background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent); }
.kbrow.is-over-top { box-shadow: inset 0 2px 0 var(--accent); }
.kbrow.is-over-bottom { box-shadow: inset 0 -2px 0 var(--accent); }
.kbrow__twist {
  flex: none; width: 16px; height: 16px; display: grid; place-items: center;
  border: none; background: none; cursor: pointer; padding: 0;
  color: var(--text-faint); font-size: var(--t-2xs); font-family: inherit;
  transition: transform var(--fast);
}
.kbrow__twist.is-open { transform: rotate(90deg); }
/* У конечной страницы на этом месте пустое место того же размера, а не
   приглушённая кнопка: строки дерева стоят на одной линии, а нажимать
   там нечего и не на что наводиться. */
.kbrow__twist.is-empty { cursor: default; }
/* Просвет после значка: «🧭Как мы работаем» слипалось в каждой строке дерева */
.kbrow__icon { flex: none; font-size: var(--t-sm); line-height: 1; margin-right: var(--s-1); }
.kbrow__name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kbrow__act {
  flex: none; width: 20px; height: 20px; border: none; background: none; padding: 0;
  border-radius: var(--radius-sm); color: var(--text-faint);
  font-family: inherit; font-size: var(--t-xs); cursor: pointer; opacity: 0;
}
.kbrow:hover .kbrow__act { opacity: 1; }
.kbrow__act:hover { background: var(--surface-3); color: var(--text); }

.kbside__foot { margin-top: var(--s-3); padding-top: var(--s-2); border-top: 1px solid var(--border); }
/* Корзина — служебное среди рабочего: воздуха перед ней больше */
.kbside__foot--apart { margin-top: var(--s-5); }

/* Наверху дерева — то, к чему возвращаются: закладки и последнее правленое */
.kbside__label {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .06em;
  font-weight: 600; color: var(--text-faint);
  padding: var(--s-2) var(--s-2) var(--s-1);
}
.kbside__label:first-child { padding-top: 0; }
.kbrow--quick { padding-left: var(--s-2); }

/* Где страницу закрепили: связь была односторонней — из проекта в базу
   дорога есть, обратно не было */
.kblinked { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; margin-bottom: var(--s-4); }
.kblinked__label {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .06em;
  font-weight: 600; color: var(--text-faint);
}
.kblinked__item {
  border: 1px solid var(--border); background: none; cursor: pointer; font: inherit;
  font-size: var(--t-xs); color: var(--text-dim);
  padding: 1px var(--s-2); border-radius: var(--radius-full);
}
.kblinked__item:hover { border-color: var(--accent); color: var(--accent); }

/* Оглавление длинной страницы: заголовки в ней уже размечены, а листалась
   она вслепую. Появляется от трёх заголовков — от двух в нём нет смысла. */
.kbtoc {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--s-05); margin: 0 0 var(--s-5);
  padding: var(--s-3) var(--s-4);
  background: var(--surface-2); border-radius: var(--radius);
}
.kbtoc__label {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .06em;
  font-weight: 600; color: var(--text-faint); margin-bottom: var(--s-1);
}
.kbtoc__item {
  border: none; background: none; cursor: pointer; font: inherit;
  font-size: var(--t-sm); color: var(--text-dim); text-align: left;
  padding: var(--s-05) 0; line-height: 1.35;
}
.kbtoc__item:hover { color: var(--accent); }
.kbtoc__item--h2 { padding-left: var(--s-3); }
.kbtoc__item--h3 { padding-left: var(--s-5); font-size: var(--t-xs); }
.kbside__trash {
  display: flex; align-items: center; gap: var(--s-2); width: 100%;
  padding: var(--s-1) var(--s-2); border: none; background: none; border-radius: var(--radius);
  color: var(--text-faint); font: inherit; font-size: var(--t-sm); cursor: pointer; text-align: left;
}
.kbside__trash:hover { background: var(--surface-2); color: var(--text); }
.kbside__trash.is-active { color: var(--text); font-weight: 600; }
.kbside__n {
  margin-left: auto; font-family: var(--mono); font-size: var(--t-2xs); color: var(--text-faint);
}

/* Находки поиска встают на место дерева */
.kbfind {
  display: block; width: 100%; text-align: left; border: none; background: none;
  padding: var(--s-2); border-radius: var(--radius); cursor: pointer; color: var(--text);
  font: inherit;
}
.kbfind:hover { background: var(--surface-2); }
.kbfind__head { display: flex; gap: var(--s-2); align-items: center; font-size: var(--t-sm); }
.kbfind small {
  display: block; margin-top: var(--s-05); color: var(--text-faint);
  font-size: var(--t-2xs); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* Шапка документа */
.kbhead {
  display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-4);
  min-height: 32px;
}
.kbhead .spacer { flex: 1 1 auto; }
.kbtrail {
  display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap;
  font-size: var(--t-xs); color: var(--text-faint); min-width: 0;
}
.kbtrail__sep { color: var(--text-faint); }
.kbtrail__link {
  border: none; background: none; padding: var(--s-05) var(--s-1); border-radius: var(--radius-sm);
  color: var(--text-dim); font: inherit; font-size: var(--t-xs); cursor: pointer;
}
.kbtrail__link:hover { background: var(--surface-2); color: var(--text); }
.kbtrail__here { color: var(--text-dim); padding: var(--s-05) var(--s-1); }
.kbstatus { font-size: var(--t-2xs); color: var(--text-faint); }
.kbstatus.is-ok { color: var(--ok); }
.kbstatus.is-bad { color: var(--critical); }
/* Расхождение правок — не сообщение, а вопрос: по нему нажимают */
.kbstatus.is-ask { cursor: pointer; text-decoration: underline dotted; }
.kbnote {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  background: var(--warn-soft); color: var(--warn);
  padding: var(--s-2) var(--s-3); border-radius: var(--radius); font-size: var(--t-sm);
  margin-bottom: var(--s-4);
}

/* Название страницы */
.kbtitlerow { display: flex; align-items: flex-start; gap: var(--s-3); margin-bottom: var(--s-4); }
.kbicon {
  flex: none; border: none; background: none; cursor: pointer; padding: 0;
  font-size: var(--t-2xl); line-height: 1.2; border-radius: var(--radius-sm);
}
.kbicon:hover { background: var(--surface-2); }
.kbtitle {
  flex: 1; min-width: 0; font-size: var(--t-2xl); font-weight: 700; line-height: 1.2;
  outline: none; margin: 0;
}
.kbtitle:empty::before, .kbb__text:empty::before, .kbb__cap:empty::before {
  content: attr(data-ph); color: var(--text-faint); pointer-events: none;
}

/* Блоки */
.kbblocks { display: flex; flex-direction: column; }
.kbb {
  position: relative; display: flex; align-items: flex-start; gap: var(--s-1);
  padding-left: calc(var(--ind, 0) * var(--s-5));
  transition: background var(--fast);
}
.kbb.is-over-top { box-shadow: inset 0 2px 0 var(--accent); }
.kbb.is-over-bottom { box-shadow: inset 0 -2px 0 var(--accent); }
.kbb.is-dragging { opacity: .4; }
.kbb__gutter {
  position: absolute; left: calc(var(--ind, 0) * var(--s-5) - var(--s-6)); top: var(--s-05);
  display: flex; gap: var(--s-05); opacity: 0; transition: opacity var(--fast);
}
.kbb:hover .kbb__gutter, .kbb:focus-within .kbb__gutter { opacity: 1; }
.kbb__plus, .kbb__handle {
  width: 22px; height: 22px; display: grid; place-items: center;
  border: none; background: none; border-radius: var(--radius-sm);
  color: var(--text-faint); font-family: inherit; font-size: var(--t-xs); cursor: grab;
}
.kbb__plus { cursor: pointer; }
.kbb__plus:hover, .kbb__handle:hover { background: var(--surface-2); color: var(--text); }
.kbb__body { flex: 1; min-width: 0; display: flex; align-items: flex-start; gap: var(--s-2); }
.kbb__text {
  flex: 1; min-width: 0; outline: none; padding: var(--s-1) 0;
  line-height: 1.6; font-size: var(--t-base); overflow-wrap: anywhere;
}
.kbb__text a { color: var(--accent); }
.kbb__text code {
  font-family: var(--mono); font-size: var(--t-mono);
  background: var(--surface-2); padding: 0 var(--s-1); border-radius: var(--radius-sm);
}
.kbb--h1 .kbb__text { font-size: var(--t-xl); font-weight: 700; line-height: 1.3; padding-top: var(--s-4); }
.kbb--h2 .kbb__text { font-size: var(--t-lg); font-weight: 700; line-height: 1.35; padding-top: var(--s-3); }
.kbb--h3 .kbb__text { font-size: var(--t-md); font-weight: 600; padding-top: var(--s-2); }
.kbb__mark {
  flex: none; width: 20px; text-align: center; color: var(--text-faint);
  font-size: var(--t-base); line-height: 1.6; padding: var(--s-1) 0;
}
.kbb__mark--num { font-family: var(--mono); font-size: var(--t-mono); }
.kbb__check { flex: none; margin-top: var(--s-2); accent-color: var(--accent); }
.kbb.is-done .kbb__text { color: var(--text-faint); text-decoration: line-through; }
.kbb--quote .kbb__body {
  border-left: 2px solid var(--border-strong); padding-left: var(--s-3);
}
.kbb--quote .kbb__text { color: var(--text-dim); font-style: italic; }
.kbb--callout .kbb__body {
  background: var(--accent-soft); border-radius: var(--radius);
  padding: var(--s-2) var(--s-3); margin: var(--s-1) 0;
}
.kbb__cicon {
  flex: none; border: none; background: none; cursor: pointer; padding: 0;
  font-size: var(--t-md); line-height: 1.5;
}
.kbb--code .kbb__body { background: var(--surface-2); border-radius: var(--radius); }
.kbb--code .kbb__text {
  font-family: var(--mono); font-size: var(--t-mono); line-height: 1.55;
  padding: var(--s-3); white-space: pre-wrap; margin: 0;
}
.kbb__hr { flex: 1; border: none; border-top: 1px solid var(--border); margin: var(--s-4) 0; }
.kbb__figure { flex: 1; min-width: 0; margin: var(--s-2) 0; }
.kbb__img { display: block; max-width: 100%; height: auto; border-radius: var(--radius); }
.kbb__cap {
  outline: none; font-size: var(--t-xs); color: var(--text-faint);
  margin-top: var(--s-1); padding: var(--s-05) 0;
}
.kbtail { min-height: 120px; cursor: text; }

/* Вложенные страницы под текстом */
.kbkids { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-4); }
.kbkids__head {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint); margin-bottom: var(--s-1);
}
.kbkid {
  display: flex; align-items: center; gap: var(--s-2); width: 100%; text-align: left;
  padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--t-sm);
  cursor: pointer; transition: border-color var(--fast), box-shadow var(--fast);
}
.kbkid:hover { border-color: var(--border-strong); box-shadow: var(--shadow-xs); }
.kbkid--new { border-style: dashed; color: var(--text-faint); }
.kbkid--new:hover { color: var(--accent); border-color: var(--accent); }
.kbfoot {
  margin-top: var(--s-6); padding-top: var(--s-3); border-top: 1px solid var(--border);
  font-size: var(--t-2xs); color: var(--text-faint);
}

/* Начальный экран и корзина */
.kbstart__title { font-size: var(--t-2xl); font-weight: 700; margin-bottom: var(--s-2); }
.kbstart__head {
  margin-top: var(--s-6); margin-bottom: var(--s-2);
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint);
}
.kbstart__list { display: flex; flex-direction: column; gap: var(--s-2); }
.kbcard {
  display: flex; align-items: center; gap: var(--s-3); width: 100%; text-align: left;
  padding: var(--s-3); border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text); font: inherit; cursor: pointer;
  transition: border-color var(--fast), box-shadow var(--fast);
}
.kbcard:hover { border-color: var(--border-strong); box-shadow: var(--shadow-xs); }
.kbcard--trash { cursor: default; }
.kbcard--trash:hover { border-color: var(--border); box-shadow: none; }
.kbcard__icon { flex: none; font-size: var(--t-md); line-height: 1; }
.kbcard__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s-05); }
.kbcard__name { font-size: var(--t-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kbcard__main small { font-size: var(--t-2xs); color: var(--text-faint); }

/* Меню базы знаний: своё, потому что kb.js не тянет за собой app.js */
.kbpop {
  position: fixed; z-index: 60; min-width: 200px; max-width: 280px;
  max-height: 60vh; overflow: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: var(--s-1); display: flex; flex-direction: column; gap: 1px;
}
.kbpop__row {
  display: flex; align-items: center; gap: var(--s-2); width: 100%; text-align: left;
  padding: var(--s-2); border: none; background: none; border-radius: var(--radius-sm);
  color: var(--text); font: inherit; font-size: var(--t-sm); cursor: pointer;
}
.kbpop__row:hover, .kbpop__row.is-cursor { background: var(--surface-2); }
.kbpop__row.is-active { color: var(--accent); font-weight: 600; }
.kbpop__row.is-danger { color: var(--critical); }
.kbpop__icon { flex: none; width: 18px; text-align: center; color: var(--text-faint); }
.kbpop__main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.kbpop__main small { font-size: var(--t-2xs); color: var(--text-faint); }
.kbpop__sep { height: 1px; background: var(--border); margin: var(--s-1) 0; }
.kbpop__head {
  padding: var(--s-2) var(--s-2) var(--s-1);
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em;
  font-weight: 600; color: var(--text-faint);
}
.kbpop--icons { max-width: 300px; }
.kbicons { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--s-05); }
.kbicons__b {
  width: 30px; height: 30px; display: grid; place-items: center;
  border: none; background: none; border-radius: var(--radius-sm);
  font-size: var(--t-md); cursor: pointer;
}
.kbicons__b:hover, .kbicons__b.is-active { background: var(--surface-2); }

/* Телефон: дерево уезжает в выдвижную полку, документ занимает весь экран */
@media (max-width: 760px) {
  .kb { grid-template-columns: minmax(0, 1fr); gap: var(--s-3); }
  .kbside { display: none; position: static; }
  .kb.is-side-open .kbside { display: block; }
  .kbdoc { max-width: 100%; }
  .kbb__gutter { display: none; }
  .kbtail { min-height: 60px; }
}

/* ── Комплектация ─────────────────────────────────────────────────────────
   Раздел отвечает на один вопрос — успеваем ли к монтажу, — и всё заметное
   здесь про срок оплаты: он горит красным, он же ромбом на диаграмме.
   Доска переиспользует колонки и карточки канбана: две разные доски в одном
   приложении читались бы как две разные программы. */

/* Панель графика закупок повторяет панель задач до пикселя: обе половины
   комплектации — один раздел, и переключение между ними не должно двигать
   ни кнопку добавления, ни всё, что под ней. Раньше у графика панель была
   без подложки и с другими полями, и страница при переходе заметно ехала. */
.procbar {
  display: flex; align-items: center; gap: var(--s-2);
  flex-wrap: wrap; row-gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border); border-radius: var(--radius);
  margin-bottom: var(--s-2);
}
.procbar__left, .procbar__right {
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
}

.procsums { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; }
.procsum { display: inline-flex; align-items: baseline; gap: var(--s-2); }
.procsum__label {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .04em;
  font-weight: 600; color: var(--text-faint);
}
.procsum__value { font-size: var(--t-sm); font-weight: 600; color: var(--text); }

.chip--late {
  background: var(--critical-soft); color: var(--critical); font-weight: 600;
}

/* ── Карточка позиции ── */

.proccard.is-late { box-shadow: inset 3px 0 0 var(--critical), var(--shadow-xs); }
.proccard.is-soon { box-shadow: inset 3px 0 0 var(--warn), var(--shadow-xs); }

.procdate {
  font-size: var(--t-xs); font-weight: 600; color: var(--text-dim);
  padding: var(--s-05) var(--s-2); border-radius: var(--radius-sm);
  background: var(--surface-2); white-space: nowrap;
}
.procdate.is-late { background: var(--critical-soft); color: var(--critical); }
.procdate.is-soon { background: var(--warn-soft); color: var(--warn); }
.procdate.is-blank { background: none; color: var(--text-faint); font-weight: 500; }
.procmoney {
  font-family: var(--mono); font-size: var(--t-mono); color: var(--text-dim);
  white-space: nowrap;
}

/* ── Диаграмма поставки ──
   Своя, а не гант задач: строка здесь — четыре отрезка подряд, и вся картина
   умещается по ширине без прокрутки. Горизонт комплектации — месяцы, и видеть
   его целиком важнее, чем разглядывать отдельные дни. */

.procchart { padding: var(--s-4); }
.pchart {
  display: grid; grid-template-columns: minmax(140px, 220px) minmax(0, 1fr);
  align-items: center; gap: var(--s-1) var(--s-3);
}
.pchart__side { min-height: 1px; }
.pchart__head { display: flex; height: 22px; align-items: center; }
.pchart__month {
  font-size: var(--t-2xs); color: var(--text-faint); font-weight: 600;
  border-left: 1px solid var(--border); padding-left: var(--s-2);
  overflow: hidden; white-space: nowrap; box-sizing: border-box;
}
.pchart__name {
  display: flex; align-items: center; gap: var(--s-2); min-width: 0;
  border: none; background: none; cursor: pointer; text-align: left;
  font: inherit; font-size: var(--t-sm); color: var(--text);
  padding: var(--s-1) 0;
}
.pchart__name span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pchart__name:hover { color: var(--accent); }
.pchart__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }

.pchart__lane {
  position: relative; height: 22px; border-radius: var(--radius-sm);
  background: var(--surface-2);
}
/* Подложка строки горит вместе с ромбом: на десяти строках глаз ищет цвет,
   а не приглядывается к форме значка */
.pchart__lane.is-late { background: var(--critical-soft); }
.pchart__lane.is-soon { background: var(--warn-soft); }
.pchart__bar { position: absolute; top: 5px; height: 12px; border-radius: var(--radius-sm); }
.pchart__bar--mount { opacity: .85; }
/* Узловые точки: план — пустой ромб, факт — залитый. Форма одна, потому что
   это одна и та же точка: сравнивают их положение, а не рисунок. Опоздавший
   факт красный — расстояние между ромбами и так видно, но цветом опоздание
   читается без измерения. */
.pchart__pt {
  position: absolute; top: 5px; width: 12px; height: 12px; margin-left: -6px;
  transform: rotate(45deg); border-radius: 2px;
}
.pchart__pt--plan {
  background: var(--surface); border: 2px solid var(--text-dim);
}
.pchart__pt--fact {
  background: var(--text); border: 2px solid var(--surface);
}
.pchart__pt--fact.is-late { background: var(--critical); }
.pchart__today {
  position: absolute; top: -2px; bottom: -2px; width: 1px;
  background: var(--today); opacity: .7;
}

.pchart__legend {
  display: flex; gap: var(--s-4); flex-wrap: wrap;
  margin-top: var(--s-4); padding-top: var(--s-3); border-top: 1px solid var(--border);
  font-size: var(--t-xs); color: var(--text-dim);
}
.pchart__leg { display: inline-flex; align-items: center; gap: var(--s-2); }
.pchart__sw { width: 12px; height: 8px; border-radius: 2px; flex: none; }
.pchart__sw--plan {
  width: 10px; height: 10px; background: var(--surface);
  border: 2px solid var(--text-dim); transform: rotate(45deg); border-radius: 2px;
}
.pchart__sw--fact {
  width: 10px; height: 10px; background: var(--text);
  transform: rotate(45deg); border-radius: 2px;
}
.pchart__sw--today { width: 2px; height: 12px; background: var(--today); border-radius: 0; }

/* Ячейка узловой точки в таблице: план сверху, факт под ним и тише.
   Пустой строки «—» под каждой датой нет намеренно — она удваивала бы
   высоту таблицы ради сообщения «ещё не наступило», которое и так видно. */
.proccell__fact { font-size: var(--t-xs); color: var(--text-dim); }
.proccell__fact.is-late { color: var(--critical); font-weight: 600; }
.proccell__delta { font-variant-numeric: tabular-nums; }

/* Разделители между итогами: подряд они читались одной строкой */
.procsum__sep { width: 1px; height: 18px; background: var(--border); flex: none; }
.procsum--all .procsum__value { color: var(--text); font-weight: 700; }
.chip--late.is-on { background: var(--critical); color: var(--surface); }

/* Сортировка списка по столбцам */
.proctable th.is-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.proctable th.is-sortable:hover { color: var(--text); }
.proctable th.is-sorted { color: var(--accent); }
.sortmark { margin-left: var(--s-1); font-size: var(--t-2xs); }

/* ── Список ── */

.proctable .is-num {
  text-align: right; font-family: var(--mono); font-size: var(--t-mono);
  white-space: nowrap;
}
.proctable tr.is-click { cursor: pointer; }
.proctable tr.is-click:hover { background: var(--surface-2); }
.proctable td.is-late { color: var(--critical); font-weight: 600; }
.proctable td.is-soon { color: var(--warn); font-weight: 600; }
.proctable tfoot td { border-top: 1px solid var(--border-strong); font-weight: 600; }
.proctable tfoot .procsum { margin-right: var(--s-4); }
.procstage { display: inline-flex; align-items: center; gap: var(--s-2); white-space: nowrap; }
.procstage__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }

/* ── Окно позиции ── */

/* Бумаги лота: КП, счёт, накладная приходят по очереди */
.procdocs { display: flex; flex-direction: column; gap: var(--s-2); align-items: flex-start; }
.procdoc { display: flex; gap: var(--s-2); width: 100%; }
.procdoc input:first-child { flex: 0 0 34%; }
.procdoc input:nth-child(2) { flex: 1; min-width: 0; }

.procgrid { display: grid; gap: var(--s-3); }
.procgrid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.procgrid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.procdatewrap { display: grid; }
/* Число и счёт дней стоят вместе: «45» без «кал./раб.» ничего не значит */
.procdur { display: flex; gap: var(--s-1); }
.procdur input { min-width: 0; }
.procdur__unit { flex: none; width: 64px; padding-inline: var(--s-2); }

.modal__sub {
  margin: var(--s-5) 0 var(--s-2); font-size: var(--t-2xs); font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint);
}
.field__note { display: block; margin-top: var(--s-1); }

/* Расчёт: то, ради чего окно и открывают. Меняешь срок поставки — видно,
   как уезжает день оплаты, и не надо ничего сохранять, чтобы это увидеть. */
.proccalc {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5);
  margin-top: var(--s-3); padding: var(--s-3) var(--s-4);
  background: var(--surface-2); border-radius: var(--radius);
}
.proccalc__step { display: flex; flex-direction: column; gap: var(--s-05); }
.proccalc__label {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .04em;
  font-weight: 600; color: var(--text-faint);
}
.proccalc__value { font-size: var(--t-sm); font-weight: 600; color: var(--text); }
.proccalc__step.is-key .proccalc__value { color: var(--accent); }
.proccalc__step.is-soon .proccalc__value { color: var(--warn); }
.proccalc__step.is-late .proccalc__value { color: var(--critical); }
.proccalc__warn {
  flex-basis: 100%; font-size: var(--t-xs); color: var(--critical);
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-1);
}

@media (max-width: 760px) {
  .procgrid--2, .procgrid--4 { grid-template-columns: minmax(0, 1fr); }
  .pchart { grid-template-columns: minmax(96px, 40%) minmax(0, 1fr); }
  .procsums { gap: var(--s-3); }
}

/* Телеграм в админке: списки людей и чатов — строками, а не карточками.
   Их десяток, и каждая строка отвечает на один вопрос: кто, под каким ником
   и здоровался ли уже с ботом. */
.tgpeople, .tgevents { display: flex; flex-direction: column; gap: var(--s-1); }
.tgevents { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.tgperson {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) 0; border-bottom: 1px solid var(--border);
}
.tgperson:last-child { border-bottom: none; }
.tgperson > :first-child { flex: 1; min-width: 0; }
.tgperson .note { flex: none; }
/* Галочки человека — свёрткой в его же строке. Занимают всю ширину и
   переносятся под неё: девять переключателей в строку не встанут, а
   развёрнуты они по одному и ненадолго. */
.tgperson__fold { flex: 0 0 100%; order: 9; font-size: var(--t-xs); }
.tgperson__fold > summary {
  cursor: pointer; list-style: none; color: var(--text-faint);
  padding: var(--s-1) 0; display: inline-flex; align-items: center; gap: var(--s-1);
}
.tgperson__fold > summary::-webkit-details-marker { display: none; }
.tgperson__fold > summary::before { content: '▸'; transition: transform var(--fast); }
.tgperson__fold[open] > summary::before { transform: rotate(90deg); }
.tgperson__fold > summary:hover { color: var(--text-dim); }
.tgperson__prefs { padding: var(--s-1) 0 var(--s-2) var(--s-4); }
.tgperson__prefs .umolrow { padding: var(--s-1) 0; border-bottom: none; }
.tgperson__prefs .umolrow__name { flex-basis: 220px; }
/* Мелкий переключатель: три коротких слова, а не три кнопки в полный рост */
.seg--sm button { padding: 3px var(--s-2); font-size: var(--t-2xs); }

/* Ключ доступа: его надо выделить и скопировать, поэтому он набран
   моноширинным и переносится, а не уезжает за край окна. */
.tokenbox {
  margin: 0 0 var(--s-2); padding: var(--s-3);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: var(--mono); font-size: var(--t-xs); line-height: 1.5;
  white-space: pre-wrap; word-break: break-all; user-select: all;
}
.tokenbox--conf { word-break: normal; max-height: 260px; overflow: auto; }

/* ── Потянуть, чтобы обновить ─────────────────────────────────────────────
   Значок висит над содержимым и едет за пальцем. Только на телефоне: на
   компьютере есть F5, а перехватывать колесо мыши незачем.

   overscroll-behavior на странице обязателен. Без него Chrome на андроиде
   показывает поверх нашего свой значок обновления и перезагружает вкладку
   целиком — то есть заново качает приложение вместо того, чтобы перерисовать
   экран. Два значка на один жест человек читает как поломку. */
body { overscroll-behavior-y: contain; }

.ptr {
  position: fixed; top: 0; left: 50%; z-index: 60;
  transform: translate(-50%, 0);
  width: var(--s-6); height: var(--s-6);
  margin-top: calc(var(--s-5) * -1);
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-full);
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
  pointer-events: none;
}
.ptr.is-hiding { transition: transform .2s ease, opacity .2s ease; opacity: 0; }
.ptr__spin {
  width: var(--s-4); height: var(--s-4);
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: var(--radius-full);
  /* Пока тянут — стрелка стоит: крутиться ей не с чего, ничего ещё не
     происходит. Довели до порога — значок окрашивается, и по нему видно,
     что отпускать можно */
  opacity: .55;
}
.ptr.is-ready .ptr__spin { opacity: 1; }
.ptr.is-spin .ptr__spin { animation: ptr-spin .7s linear infinite; }
@keyframes ptr-spin { to { transform: rotate(360deg); } }

/* Кому анимация мешает — тому её и не надо */
@media (prefers-reduced-motion: reduce) {
  .ptr.is-spin .ptr__spin { animation: none; }
  .ptr.is-hiding { transition: none; }
}

/* ── Лента проекта: сложенный раздел, а не приписка ────────────────────────
   Обычная складка (.fold) набрана мелкой прописной — так подписана «История
   изменений» внутри задачи, и там это верно: служебная строка. Лента же
   заменяет собой раздел с заголовком, и если оставить ей тот же кегль,
   свёрнутая она читается как служебная строка и её перестают замечать. */
.fold--feed { border-top: none; margin-top: var(--s-5); padding-top: 0; }
/* «Поручил» и «Наблюдаю» на главной. Заголовок крупнее панельного: это
   раздел страницы, а не складка внутри карточки, — но тише «Моих задач»:
   своя работа остаётся главной, чужая стоит под ней и ждёт, когда её
   откроют. Счётчик и пояснение — в той же строке, чтобы по закрытому
   блоку было видно, есть ли там что-нибудь. */
/* Отступ между блоками — s-5, а не s-6: 32 пикселя разрывали главную на
   три отдельных экрана, между которыми нужно ехать глазами. Это части
   одного списка дел, а не разные страницы. */
.fold--home { border-top: none; margin-top: var(--s-5); padding-top: 0; }
/* По базовой линии, а не по центру: заголовок 17px, счётчик и пояснение
   мельче, и при выравнивании по центру мелкое всплывало над словом.
   Одна ось у всех трёх — это то, что читается как одна строка. */
.fold--home > summary {
  align-items: baseline;
  font-size: var(--t-md); text-transform: none; letter-spacing: -.01em;
  color: var(--text); font-weight: 600;
}
/* Распорка в шапке складки растягивается — как в шапке «Моих задач».
   Правила для неё тут не было вовсе, и отборы «Поручил» стояли вплотную
   к заголовку, а у «Моих задач» — у правого края. Три заголовка одного
   экрана, и органы у них в разных местах. */
.fold--home > summary > .spacer { flex: 1 1 auto; min-width: var(--s-3); }
/* Стрелка складки уехала из-под заголовка к правому краю.
   Слева она стояла первой в строке и сдвигала заголовок «Поручил» на
   полсантиметра вправо — в ряду с «Моими задачами», у которых складки нет,
   три заголовка одного экрана начинались от двух разных линий. Справа она
   в этот ряд не вмешивается вовсе.
   ::after, а не ::before: он идёт последним среди детей строки, то есть
   правее и счётчика, и кнопки, и отборов. */
.fold--home > summary::before { display: none; }
.fold--home > summary::after {
  content: '▸'; align-self: center; flex: none; order: 99;
  display: grid; place-items: center; width: 28px; height: 28px;
  font-size: var(--t-base); line-height: 1; color: var(--text-faint);
  border-radius: var(--radius); background: var(--surface-2);
  transition: transform var(--fast), color var(--fast), background var(--fast);
}
.fold--home[open] > summary::after { transform: rotate(90deg); }
.fold--home > summary:hover::after { color: var(--text); background: var(--surface-3); }
/* Счёт задач в заголовке — тот же .hblock__count, что у «Моих задач»:
   «12 всего · 3 просрочено». Сперва здесь стояло голое число, да ещё и
   моноширинным шрифтом от подписи группы, и три заголовка на одном экране
   считали задачи тремя разными способами.
   
   Вес обычный: жирная цифра рядом с жирным словом читается как второе слово.
   Точка перед пояснением — иначе «Поручил 12 что я поставил другим»
   складывается в одну фразу, где число оказывается частью предложения. */
.fold--home > summary .hblock__count { font-weight: 400; letter-spacing: 0; }
.fold--home > summary .note { font-weight: 400; letter-spacing: 0; }
.fold--home > summary:hover { color: var(--accent); }
.fold--home[open] > summary { margin-bottom: var(--s-3); }

/* Состояние и исполнители на карточке — только в «Поручил» и «Наблюдаю».
   Статус тихой плашкой с точкой цвета колонки: это справка, а не орган
   управления, и выглядеть как кнопка он не должен. */
.kcard__status {
  display: inline-flex; align-items: center; gap: var(--s-1);
  font-size: var(--t-2xs); color: var(--text-dim); white-space: nowrap;
}
.kcard__status::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--st-color, var(--text-faint));
}
/* Обёртка вокруг плашки статуса: нажатие меняет статус.
   Кнопка ничего не рисует — вид остаётся у самой плашки, иначе в строке
   появился бы второй прямоугольник вокруг первого. */
.kcard__statusbtn {
  border: none; background: none; padding: 0; margin: 0; font: inherit;
  color: inherit; cursor: pointer; display: inline-flex; align-items: center;
  flex: none; border-radius: var(--radius-full);
  transition: opacity var(--fast);
}
.kcard__statusbtn:hover .kcard__status,
.kcard__statusbtn.is-open .kcard__status { color: var(--text); }

/* Лица стопкой, как в шапке проекта: до трёх, дальше «+N» */
.kcard__who { display: inline-flex; align-items: center; flex: none; }
.kcard__who .avatar { margin-left: -6px; border: 2px solid var(--raised); }
.kcard__who .avatar:first-child { margin-left: 0; }
.kcard__who-n { font-size: var(--t-2xs); color: var(--text-faint); margin-left: var(--s-1); }

/* Кнопки прямо на карточке: исполнитель, приоритет, сложность. Выглядят как
   были — заливки и рамки нет, — но нажимаются. Кнопка, которая выглядит как
   кнопка, среди меток превратила бы карточку в панель управления; здесь
   достаточно, чтобы под курсором знак оживал. */
/* Метка-кнопка и срок-кнопка на карточке в колонке: выглядят как метка,
   но по ним меняется значение. Заливки и рамки кнопки нет намеренно —
   карточка не должна превращаться в панель управления; достаточно, чтобы
   знак оживал под курсором. */
/* Только семейство, а не `font: inherit`.

   Сокращение `font` сбрасывает разом всё — и кегль в том числе, на кегль
   родителя. Правило стоит ниже `.ktag`, и нажимаемый ярлык оказывался
   на две ступени крупнее ненажимаемого: одна и та же метка в одной строке
   имела два разных размера, смотря можно ли её поменять. */
.ktag--act { cursor: pointer; font-family: inherit; border: none; }
.ktag--act:hover { filter: brightness(.95); }
.kcard__date--act {
  background: none; border: none; padding: 0; font: inherit; color: inherit;
  cursor: pointer; border-radius: var(--radius-sm);
}
.kcard__date--act:hover { color: var(--accent); }
.kcard__whobtn, .kcard__hotbtn {
  border: none; background: none; padding: 0; margin: 0; font: inherit;
  color: inherit; cursor: pointer; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; flex: none;
  transition: opacity var(--fast), transform var(--fast);
}
.kcard__whobtn:hover, .kcard__hotbtn:hover { transform: scale(1.08); }
.kcard__whobtn.is-open, .kcard__hotbtn.is-open { transform: scale(1.08); }
/* Погасший огонёк: приоритета нет, но поднять его можно. Виден только
   при наведении на карточку — иначе серый огонёк стоял бы на каждой строке
   и означал ничего. Кружка под ним нет: пустое место не подсвечивают. */
.kcard__hot--off {
  opacity: 0; filter: grayscale(1); background: none;
  transition: opacity var(--fast);
}
.kcard--free:hover .kcard__hot--off, .kcard__hotbtn.is-open .kcard__hot--off { opacity: .45; }
.kcard__hot--off:hover { opacity: .8 !important; }

/* Кнопка «обсудить в чате» — вместо поля обсуждения в задаче. Кнопка, а не
   ссылка строкой: это выход в другую программу, и выглядеть он должен как
   действие. Название чата рядом и тише — по нему видно, куда именно ведёт. */
.talkbtn { display: inline-flex; align-items: center; gap: var(--s-2); text-decoration: none; }
.talkbtn:hover { border-color: var(--accent); color: var(--accent); }
.fold--feed > summary {
  font-size: var(--t-lg); font-weight: 600; color: var(--text);
  text-transform: none; letter-spacing: normal;
}
.fold--feed > summary::before { font-size: var(--t-md); color: var(--text-dim); }
.fold--feed > summary .note { text-transform: none; letter-spacing: normal; font-weight: 400; }

/* ── Под палец ────────────────────────────────────────────────────────────
   Блок стоит последним намеренно. Правила здесь перебивают размеры, заданные
   выше по файлу, а сила у них та же — значит решает порядок. Стоял он в
   середине, и `.pnote__act { width: 18px }` из раздела закреплённого спокойно
   его отменял: значок оставался восемнадцатипиксельным, а я считал, что
   починил. Проверка нажатием это и вскрыла.

   Всё внутри — только для сенсорных указателей. Мышь попадает и в двадцать
   два пикселя, а невидимые зоны на десктопе перехватывали бы наведение
   у соседей. */
@media (pointer: coarse) {
  /* Зона нажатия у мелких органов — невидимой подложкой, а не размером.
     
     Палец накрывает круг около сорока четырёх пикселей, а кружок хода
     у нас двадцать шесть, срок — двадцать два в высоту, значок на заметке —
     восемнадцать. Промах по такой цели стоит дорого: рядом с кружком хода
     лежит сама карточка, и мимо кнопки «закрыть» открывается задача.
     
     Раздувать их нельзя: строка задачи держится на том, что органы в ней
     мелкие и не спорят с названием. Поэтому растёт не сам знак, а его
     зона: псевдоэлемент по центру, ровно 44 на 44, невидимый и не мешающий
     соседям, потому что вынесен из потока.
     
     Только на сенсорных: мышь попадает и в двадцать два пикселя, а лишние
     невидимые квадраты на десктопе перехватывали бы наведение. */
  .stepbtn, .kcard__whobtn, .kcard__hotbtn, .kcard__date-btn,
  .pnote__act, .stick__act, .backbtn, .teamadd,
  .kcard__tags .ktag--act, .kcard__tags .ktag--put {
    position: relative;
  }
  .stepbtn::after, .kcard__whobtn::after, .kcard__hotbtn::after,
  .kcard__date-btn::after, .pnote__act::after, .stick__act::after,
  .backbtn::after, .teamadd::after,
  .kcard__tags .ktag--act::after, .kcard__tags .ktag--put::after {
    content: ''; position: absolute; top: 50%; left: 50%;
    width: 100%; height: 100%; min-width: 44px; min-height: 44px;
    transform: translate(-50%, -50%);
  }
  /* Флажки — единственное, что приходится растить по-настоящему: у них
     нет своего содержимого, и невидимая подложка не даёт им веса на глаз.
     Тринадцать пикселей — это системный флажок, каким его рисует Windows. */
  input[type='checkbox']:not(.ckrow input) { width: 22px; height: 22px; }

  /* Значки на заметке растут по-настоящему: они стоят в углу карточки,
     соседей рядом нет, и подложка им не поможет — она перекрыла бы вторую
     такую же кнопку в двух пикселях от первой. */
  .pnote__act, .stick__act { width: 32px; height: 32px; }

  /* В строке списка нажимается сама строка, а органы в ней — нет.
     
     Строка высотой 56 пикселей вмещает кружок хода, метку проекта, срок,
     огонёк и лицо. Развести их под палец нельзя: сорок четыре пикселя
     на каждый — это двести двадцать, вчетверо больше самой строки, и зоны
     перекрыли бы друг друга. Проверка нажатием это и показала: палец
     попадает в соседа.
     
     Поэтому на сенсорном строка ведёт себя как строка: нажатие открывает
     задачу, а срок, приоритет, исполнителя и метки правят внутри неё, где
     под каждый орган есть место. Кружок хода — исключение: закрыть задачу
     одним касанием и есть то, ради чего список открывают.
     
     pointer-events: none, а не скрытие: знаки продолжают сообщать — виден
     и срок, и приоритет, и кто делает. Нажатие просто проходит сквозь них
     к карточке. */
  .is-rows .kcard__hotbtn, .is-rows .kcard__whobtn,
  .is-rows .kcard__date-btn, .is-rows .kcard__tags .ktag--act,
  .is-rows .kcard__tags .ktag--put { pointer-events: none; }
}

/* Карандаш на карточке сотрудника: на мыши он проявляется при наведении и
   этого хватает, а на телефоне наведения нет — бледный знак без обводки
   читался помаркой на карточке, а не кнопкой. Здесь он получает вид кнопки
   и палец в сорок четыре пикселя. */
@media (pointer: coarse) {
  .pcard__act {
    opacity: 1; width: 34px; height: 34px;
    background: var(--surface-2); color: var(--text-dim);
    border: 1px solid var(--border); border-radius: var(--radius);
  }
  .pcard__act::after {
    content: ''; position: absolute; inset: 50% auto auto 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }
}

/* Ещё две цели, до которых палец не дотягивался. Обе — на сенсорном экране;
   на мыши они в добавке не нуждаются и её не получают.

   Хлебные крошки — строчка текста в двадцать один пиксель высотой. Ставить
   ей невидимую подложку в сорок четыре нельзя: снизу в восьми пикселях
   стоит стрелка «назад» со своей такой же подложкой, и зоны наложились бы.
   Растёт настоящая высота — строка крошек становится выше, и соседи просто
   отодвигаются вниз. Восемь пикселей сверху и снизу дают тридцать семь:
   для строки текста этого достаточно, палец попадает в неё, а не в соседа.

   Кнопки разделов на карточке проекта — тридцать восемь пикселей: палец
   попадает, но запаса нет. Соседей сверху и снизу у них нет — только край
   карточки, — поэтому здесь можно и нужно поставить полные сорок четыре. */
@media (pointer: coarse) {
  .crumbs button { padding: var(--s-2) 0; }
  .projcard__go .btn { min-height: 44px; }
}

/* Закрытая задача на доске: видна, но приглушена.

   Она показывается только при включённом «показать закрытые» и стоит
   вперемешку с живыми — в своей колонке. Без пометки её не отличить,
   а спутать закрытую с работой, которую ещё делать, дорого. Приглушение,
   а не перечёркивание: перечёркнутый текст в списке из десяти карточек
   читается хуже, чем не читается вовсе. */
.kcard--zakryta { opacity: .55; }
.kcard--zakryta:hover { opacity: .8; }
.kcard--zakryta .kcard__name { text-decoration: none; }

/* ── Динозаврик ──────────────────────────────────────────────────────────────
   Игра рисуется на холсте, и оттуда же берёт цвета темы, — стилей ей нужно
   ровно два: чтобы холст растягивался по ширине окна и чтобы точки остались
   точками. Сглаживание здесь враг: браузер по умолчанию размывает картинку
   при растяжении, и пиксельный ящер превращается в мыло. */
.dino__body { padding: 0; }
.dino__canvas {
  display: block; width: 100%; height: auto;
  image-rendering: pixelated;
  border-radius: var(--radius);
  /* Палец на телефоне не должен листать страницу, пока им же прыгают */
  touch-action: none;
  cursor: pointer;
}
/* Подпись в шапке окна на узком экране уезжает: там и без неё понятно,
   а «пробел» на телефоне всё равно негде нажать */
@media (max-width: 760px) {
  .dino .modal__head .note { display: none; }
}

/* Таблица рекордов динозаврика. Показывается вместо холста, в том же окне:
   отдельное окно поверх окна ради двадцати строк — это уже не игра, а
   интерфейс. Кнопка в подвале переключает туда и обратно. */
.dino__board { padding: var(--s-4); min-height: 176px; }
.dino__table { width: 100%; }
.dino__table td, .dino__table th { vertical-align: middle; }
/* Место: медали первым трём, дальше просто число. Ряд из двадцати кружочков
   превратил бы таблицу в ёлку, а «7» и так говорит всё, что нужно. */
.dino__place { font-size: var(--t-md); text-align: center; }
.dino__who { display: inline-flex; align-items: center; gap: var(--s-2); min-width: 0; }
/* Очки моноширинные и с ведущими нулями — как счётчик в самой игре:
   цифры не пляшут, и столбец читается сверху вниз одним взглядом */
.dino__score {
  text-align: right; font-family: var(--mono); font-size: var(--t-mono);
  font-variant-numeric: tabular-nums;
}
/* Своя строка подсвечена: в таблице на двадцать фамилий себя ищут первым делом */
.dino__table tr.is-me { background: var(--accent-soft); }
.dino__table tr.is-me td { font-weight: 600; }

/* ── Whiteboard: доски для рисования ──────────────────────────────────────── */

/* Сетка досок. Карточка — миниатюра и подпись под ней: доску узнают по тому,
   что на ней нарисовано, а название придумывают на бегу. */
.wbgrid {
  display: grid; gap: var(--s-4);
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.wbcard {
  display: flex; flex-direction: column; cursor: pointer; overflow: hidden;
  background: var(--raised); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  transition: border-color var(--fast), box-shadow var(--fast);
}
.wbcard:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }
/* Миниатюра всегда одной высоты: иначе ряд карточек идёт лесенкой и сетка
   перестаёт читаться рядами. Картинка вписывается целиком, а не обрезается
   по центру — на доске важен угол, где что-то нарисовано. */
.wbcard__img {
  display: block; width: 100%; height: 150px; object-fit: contain;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.wbcard__img--empty { display: grid; place-items: center; color: var(--text-faint); }
.wbcard__foot { display: flex; align-items: flex-start; gap: var(--s-2); padding: var(--s-3); }
.wbcard__main { flex: 1; min-width: 0; }
.wbcard__name {
  font-size: var(--t-base); font-weight: 600; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.wbcard__note {
  font-size: var(--t-2xs); color: var(--text-faint); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wbcard__more { flex: none; opacity: 0; transition: opacity var(--fast); }
.wbcard:hover .wbcard__more { opacity: 1; }
/* Под пальцем наведения нет — кнопка видна всегда */
@media (hover: none) { .wbcard__more { opacity: .6; } }

/* Сама доска: холст во всё, что осталось от экрана. Поля вокруг него —
   чистая потеря: рисуют на всём, что дают. */
.page--wb {
  display: flex; flex-direction: column; height: 100%;
  padding: var(--s-3) var(--s-3) var(--s-3);
}
.wb__head { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); }
.wb__head > .spacer { flex: 1; }
/* Название правится на месте, а не в окне: поле без рамки, пока в него
   не встали, — заголовок, который оказался вводом. */
.wb__title {
  width: min(320px, 40vw); height: auto; padding: var(--s-1) var(--s-2);
  font-size: var(--t-lg); font-weight: 600;
  border-color: transparent; background: transparent;
}
.wb__title:hover { border-color: var(--border); }
.wb__saved { font-size: var(--t-2xs); color: var(--text-faint); white-space: nowrap; }
/* Кто сейчас на доске — кружками в её шапке. Курсоры соседей рисует сам
   редактор; эта строка отвечает на вопрос «я тут один?», не глядя на холст. */
.wb__people { display: flex; align-items: center; gap: 3px; margin-right: var(--s-2); }
.wb__face {
  width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  color: #fff; font-size: var(--t-2xs); font-weight: 600; flex: none;
  box-shadow: 0 0 0 2px var(--surface);
}
.wb__canvas {
  flex: 1; min-height: 360px; border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
/* Редактор рисует себя сам и лезет во всю высоту родителя */
.wb__canvas .excalidraw { height: 100%; }

/* Сводка «Планирования»: одна строка над колонками. Не плитки — плитки
   просят места и веса, которых у сводки нет: её читают на бегу, по дороге
   к колонкам, ради которых сюда и заходят. */
.plansv {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--s-2);
  font-size: var(--t-sm); color: var(--text-dim); margin-bottom: var(--s-3);
}
.plansv__item b { font-weight: 600; color: var(--text); }
.plansv__item.is-bad b { color: var(--critical); }
.plansv__item.is-good b { color: var(--ok); }
.plansv__item.is-warn b { color: var(--warn); }
.plansv__sep { color: var(--text-faint); }
.plansv__quiet { color: var(--text-faint); }

/* Гамбургер разделов: показывается, только когда полоса не влезла.
   Ширину не угадываем числом — у бюро разделов может быть шесть, а может
   десять; меряет сама шапка, см. `подогнатьПолосуРазделов`. */
.roundbtn--menu { display: none; }
.topbar.is-tight .topnav { display: none; }
.topbar.is-tight .roundbtn--menu { display: grid; }
/* На телефоне разделы стоят полосой внизу, и гамбургер в шапке был бы
   третьей дверью в одно и то же место */
@media (max-width: 760px) { .roundbtn--menu { display: none !important; } }

/* ── Поставщики ──────────────────────────────────────────────────────────── */

/* Поиск первым и во всю ширину: справочник открывают с вопросом «у кого мы
   брали свет», а не «покажи всех подряд». */
.supsearch { width: 100%; height: 42px; font-size: var(--t-md); }
/* Отбор по категориям — теми же фишками, что типы задач на доске: один язык
   на всё приложение, и объяснять его второй раз не нужно. */
.supcats { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-4); }
.typechip__n {
  margin-left: var(--s-1); font-size: var(--t-2xs); color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.typechip--add { border-style: dashed; color: var(--text-faint); }

.suptbl { background: var(--surface); border-radius: var(--radius); overflow: hidden; }
.suptbl td { padding: var(--s-1) var(--s-2); vertical-align: top; }
.suptbl th { cursor: default; }
/* Заголовок — кнопка порядка. Нажатая помечена стрелкой, а не только цветом:
   по цвету не видно, вверх сортировка или вниз. */
.suptbl__th { cursor: pointer; user-select: none; white-space: nowrap; }
.suptbl__th:hover { color: var(--text); }
.suptbl__th.is-sorted { color: var(--text); }
.suptbl__ord { margin-left: 3px; font-size: var(--t-2xs); }

/* Ячейка — текст, пока в неё не встали. Рамка у каждой из сотни ячеек
   превратила бы таблицу в анкету: не видно, где данные, а где пустота. */
.supcell {
  display: flex; align-items: flex-start; gap: var(--s-1); min-height: 30px;
  padding-top: 5px; padding-bottom: 5px;
  border-radius: var(--radius-sm); padding: 0 var(--s-1); cursor: text;
  transition: background var(--fast), box-shadow var(--fast);
}
.supcell:hover { background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--border); }
/* Текст переносится, а не тянет столбец. Раньше ячейка была в одну строку,
   и «Что продаёт» с описанием на полтораста знаков растягивало столбец на
   пол-экрана, а таблицу — на четыре ширины монитора. Перенос по любому месту
   нужен ради ссылок и почты: пробелов в них нет, и без него строка всё равно
   распирала бы столбец. */
.supcell__text {
  flex: 1; min-width: 0; font-size: var(--t-sm);
  white-space: normal; overflow-wrap: anywhere;
  /* Ничего не обрезаем. Обрезанная ячейка — это потерянные данные, а базу
     разбирают ровно по тому, что в ней написано: под срез уходили и отзывы,
     и разбор аудита. Строки от этого разной высоты и таблица шире экрана —
     она и должна быть шире, для того у неё своя прокрутка вбок. */
}
.supcell__empty { flex: 1; color: var(--text-faint); font-size: var(--t-sm); }
.supcell__input {
  width: 100%; height: 28px; padding: 0 var(--s-1); font-size: var(--t-sm);
}
/* Ссылка отдельным знаком: нажатие ради правки не должно уводить со страницы */
.supcell__go {
  flex: none; color: var(--text-faint); text-decoration: none; padding: 0 2px;
  font-size: var(--t-xs);
}
.supcell__go:hover { color: var(--accent); text-decoration: none; }
/* Источник — словом в цвете, а не фишкой: столбец читают колонкой сверху
   вниз, и «работали» должно выделяться среди непроверенного, не крича. */
.suporigin { font-size: var(--t-sm); text-align: left; }
.suporigin.is-worked { color: var(--ok); font-weight: 500; }
.suporigin.is-viki { color: var(--text); }
.suporigin.is-bought { color: var(--text-faint); }
/* Две кнопки в строке — чёрный список и удаление — в один ряд, без переноса */
.suptbl__act { white-space: nowrap; }
.supcell__pick {
  background: none; border: none; padding: 0; cursor: pointer; font: inherit;
  display: flex; align-items: center; min-width: 0;
}

/* Перебранная руками строка — светло-зелёная. Отметка не про поставщика,
   а про разбор базы: две трети строк пришли пачкой из купленной таблицы,
   и без цвета не видно, докуда дошли. Цвет бледный намеренно — это фон под
   двадцатью столбцами, а не сообщение. */
.suptbl tr.is-checked td { background: color-mix(in srgb, var(--ok) 9%, transparent); }
/* Категорий у поставщика бывает пять — метки переносятся, а не режутся */
.supcell--tags .supcell__pick { flex: 1; }
.supcell__tags { display: flex; flex-wrap: wrap; gap: 3px; }

/* Страница одного поставщика: то же, что в строке таблицы, но читаемо.
   В двадцати пяти столбцах человека не покажешь, а ссылкой делятся именно
   на человека — «вот у кого брали ту плитку». */
.suppage__state {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  margin-bottom: var(--s-4);
}
.suppage__cats { margin-bottom: var(--s-5); }
.suppage__grid {
  display: grid; gap: var(--s-3) var(--s-5);
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  margin-bottom: var(--s-6);
}
.suppage__label {
  font-size: var(--t-xs); color: var(--text-dim); text-transform: uppercase;
  letter-spacing: .04em; margin-bottom: 2px;
}
.suppage__field .supcell { border: 1px solid var(--border); }
.suppage__more { grid-column: 1 / -1; justify-self: start; }
.suppage__kon { margin-bottom: var(--s-6); }
.suppage__kon h3 { margin-bottom: var(--s-2); }
.suppage__foot {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  padding-top: var(--s-4); border-top: 1px solid var(--border);
}

/* Полоса с процентами вместо трёх точек. Точки не отличают «идёт» от
   «встало»; полоса отвечает на оба вопроса сразу и говорит, сколько ещё. */
.progress { max-width: 420px; margin: var(--s-7) auto; }
.progress__head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: var(--s-2);
}
.progress__what { font-size: var(--t-sm); color: var(--text-dim); }
.progress__pct {
  font-size: var(--t-sm); font-variant-numeric: tabular-nums; color: var(--text);
}
.progress__track {
  height: 4px; border-radius: 999px; background: var(--surface-2); overflow: hidden;
}
.progress__bar {
  height: 100%; width: 0; border-radius: 999px; background: var(--accent);
  transition: width .12s linear;
}
/* Длина ответа неизвестна — полоса бегает сама, чтобы было видно: идёт */
.progress.is-vague .progress__bar {
  width: 35%; animation: progress-run 1.1s ease-in-out infinite;
}
@keyframes progress-run {
  0% { margin-left: -35%; } 100% { margin-left: 100%; }
}

/* Окно заведения поставщика: два поля в ряд, полки фишками */
.newsup__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); }
.newsup__cats {
  display: flex; flex-wrap: wrap; gap: var(--s-1);
  max-height: 190px; overflow-y: auto;
}

/* Поиск — в строке заголовка: отдельной строкой он съедал полсотни пикселей
   высоты, а их не хватает таблице.

   Полоса разложена сеткой, а не флексом. Флексом название, поле и кнопки
   вставали подряд от левого края, и на широком экране треть строки оставалась
   пустой справа. Здесь боковые колонки равны — значит поле стоит по центру
   страницы, а не по центру остатка, — а кнопки уходят к правому краю. */
.suphead {
  display: grid; align-items: center;
  grid-template-columns: 1fr minmax(200px, 560px) 1fr;
  gap: var(--s-3);
}
/* Своя нижняя граница ширины: общее правило шапок разделов ставит
   заголовку `min-width: 0`, и в сетке его колонка сжималась ниже самого
   слова — «Поставщики» вылезало из своей клетки и уезжало под поле поиска
   на экранах от 760 до 1100 точек. */
.suphead h1 { justify-self: start; min-width: max-content; }
.suphead .supsearch { min-width: 0; max-width: none; margin: 0; }
.suphead .page__head-right { justify-self: end; }
/* На телефоне поиск уходит на свою строку.

   В одну строку с названием раздела и двумя кнопками он не помещался
   и распирал страницу: сама страница уезжала вбок, и заголовок с фишками
   уходили за левый край. Поиск при этом главный орган раздела — прятать
   или ужимать его нельзя, значит место ему нужно своё. */
@media (max-width: 760px) {
  /* Сетка в три колонки на узком экране не нужна: колонок нет, всё
     переносится. Возвращаем флекс, которым и написана мобильная раскладка. */
  .suphead { display: flex; flex-wrap: wrap; }
  .suphead .supsearch { flex: 1 0 100%; order: 3; max-width: none; }
  /* Заголовку хватает своей ширины: общее правило широких разделов давало
     ему всю строку, и кнопка заведения сваливалась под него отдельным рядом
     на пустом месте. */
  .page--wide .suphead h1 { flex: 1 1 auto; }
  /* Длинное название полки не распирает ряд: «Индивидуальные
     изготовления/корпусная мебель» шире экрана целиком. */
  .typechip { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
}

/* Отметка «перебрано» — не системная галочка. Пока она рисовалась галочкой,
   её читали как «выбрать строку»: это разные вещи, и путать их нельзя. */
.supmark {
  border: 1px solid var(--border); background: none; cursor: pointer;
  border-radius: 999px; padding: 2px var(--s-2); font: inherit;
  font-size: var(--t-xs); color: var(--text-faint); white-space: nowrap;
  transition: color var(--fast), border-color var(--fast), background var(--fast);
}
.supmark:hover { color: var(--text-dim); border-color: var(--border-strong); }
.supmark.is-on {
  color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent);
  background: color-mix(in srgb, var(--ok) 10%, transparent);
}
.supcell--check { cursor: default; }
.supcell--check:hover { background: none; box-shadow: none; }

/* Выбор пачкой: своя колонка, своя галочка, свой цвет строки */
.suptbl__pick { text-align: center; vertical-align: middle; }
.suptbl__pick input { margin: 0; cursor: pointer; }
/* Переход на страницу поставщика — свой столбец сразу за выбором пачкой.
   Кнопка тихая: строк шестьсот, и шестьсот заметных кнопок легли бы решёткой
   поверх данных. Она проясняется на строке под курсором и загорается под
   самим курсором — то есть находится ровно тогда, когда её ищут. */
.suptbl__open { text-align: center; vertical-align: middle; }
.supopen {
  display: inline-grid; place-items: center;
  width: 28px; height: 28px; padding: 0;
  border: none; border-radius: var(--radius-sm);
  background: none; cursor: pointer; color: var(--text-faint);
  transition: color var(--fast), background var(--fast);
}
.suptbl tr:hover .supopen { color: var(--text-dim); }
.supopen:hover { color: var(--accent); background: var(--surface-2); }
.supopen:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pageicon { display: block; line-height: 0; }
.suptbl tr.is-picked td { background: color-mix(in srgb, var(--accent) 10%, transparent); }

/* Что можно сделать с выбранными. Появляется, когда есть кого. */
.suppick {
  display: none; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  margin-bottom: var(--s-3); padding: var(--s-2) var(--s-3);
  border: 1px solid var(--accent); border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.suppick.is-on { display: flex; }
.suppick__n { font-size: var(--t-sm); font-weight: 500; white-space: nowrap; }

/* Технический номер строки: моноширинный, тусклый, копируется нажатием */
.supcell--id { cursor: copy; }
.supid {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--t-2xs); color: var(--text-faint); overflow-wrap: anywhere;
}

/* Контакты в ячейке: у поставщика их бывает пять, и каждый — своя строка */
.supkon { display: flex; flex-direction: column; gap: 3px; width: 100%; }
.supkon__row { display: flex; flex-wrap: wrap; gap: var(--s-1); align-items: baseline; }
.supkon__name { font-size: var(--t-sm); font-weight: 500; }
.supkon__val {
  font-size: var(--t-xs); color: var(--text-dim); text-decoration: none;
  overflow-wrap: anywhere;
}
a.supkon__val:hover { color: var(--accent); }

/* Правка контакта: четыре поля в ряд, заметка под ними, крестик справа */
.konedit {
  position: relative; border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--s-3); padding-right: 40px; margin-bottom: var(--s-2);
  display: flex; flex-direction: column; gap: var(--s-2);
}
.konedit__grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2);
}
.konedit__del { position: absolute; top: var(--s-2); right: var(--s-2); }

/* Справочник на телефоне — карточками.

   Двадцать пять столбцов на экране в 375 точек — лента шириной в пятнадцать
   экранов. С телефона в справочник заходят найти поставщика и позвонить ему;
   разбирают базу за столом. */
.supcards { display: flex; flex-direction: column; gap: var(--s-2); }
.supcard {
  background: var(--surface); border-radius: var(--radius-lg);
  padding: var(--s-3); display: flex; flex-direction: column; gap: var(--s-1);
}
.supcard.is-checked { box-shadow: inset 3px 0 0 var(--ok); }
.supcard__name {
  border: none; background: none; padding: 0; text-align: left; cursor: pointer;
  font: inherit; font-size: var(--t-md); font-weight: 600; color: var(--text);
}
.supcard__cats { display: flex; flex-wrap: wrap; gap: 3px; }
.supcard__what {
  font-size: var(--t-sm); color: var(--text-dim);
  /* Две строки: описание бывает в полтораста слов, и карточка вырастала
     на пол-экрана — до следующего поставщика приходилось листать. */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.supcard__kon { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2); }
.supcard__who { font-size: var(--t-sm); font-weight: 500; }
.supcard__link { font-size: var(--t-sm); text-decoration: none; }
.supcard__foot {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s-2); margin-top: 2px;
}
.supcard__go { font-size: var(--t-sm); text-decoration: none; flex: none; }
.supcards__more { padding: var(--s-3) 0; text-align: center; }

/* Выбор поставщика поиском: списком шестьсот строк не выбирают */
.pickbox { max-height: 320px; overflow-y: auto; margin-top: var(--s-3); }
.pickbox__row {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-2);
  width: 100%; text-align: left; padding: var(--s-2); border: none; background: none;
  cursor: pointer; border-radius: var(--radius-sm); font: inherit;
}
.pickbox__row:hover { background: var(--surface-2); }
.konedit__move { position: absolute; top: var(--s-2); right: 40px; }

/* Разбор повторов. Куча — это строки об одном поставщике; отмеченная
   останется, остальные подошьются к ней. */
.dupgroup {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--s-3); margin-bottom: var(--s-3);
}
.dupgroup__why { margin-bottom: var(--s-2); }
.dupgroup__foot { margin-top: var(--s-2); display: flex; justify-content: flex-end; }
.duprow {
  display: flex; align-items: baseline; gap: var(--s-2); cursor: pointer;
  padding: var(--s-1) 0;
}
.duprow input { margin: 0; flex: none; }
.duprow__name { font-weight: 500; flex: none; }
.duprow__meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Отборы, которых не бывает у категорий: сегмент и оценка. Списками,
   а не фишками — значений мало, и в ряду с полусотней категорий они
   читались бы ещё одной категорией. */
.supfilters {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  margin-bottom: var(--s-3);
}
.supfilters__n { margin-left: var(--s-2); white-space: nowrap; }
/* Оценка аудита — буквой в цвете. Читать её приходится колонкой сверху вниз,
   и цвет отвечает на «где тут плохо» быстрее, чем чтение букв. */
.suptbl__grade .supcell__text { font-weight: 600; }
.suptbl__grade.is-A .supcell__text { color: var(--ok); }
.suptbl__grade.is-B .supcell__text { color: var(--text); }
.suptbl__grade.is-C .supcell__text { color: var(--warn); }
.suptbl__grade.is-D .supcell__text { color: var(--critical); }
/* Таблица шире экрана и листается вбок: столбцов пятнадцать, и ужимать их
   до нечитаемости хуже, чем листать. Ширины заданы в точках, а не в долях:
   в долях «Сегмент» получал столько же, сколько «Что продаёт». */
/* Ширины столбцов — предел, а не запрос: fixed держит их ровно такими,
   какие заданы, и содержимое переносится внутри. min-width таблице ставит
   сама страница, суммой видимых столбцов: столбцы включаются и выключаются,
   и зашитое сюда число врало бы при каждом переключении. */
.suptbl { table-layout: fixed; }
/* Поле правки на несколько строк — для тех полей, где лежит абзац */
.supcell__input--long {
  height: auto; min-height: 120px; resize: vertical; line-height: 1.4;
  padding: var(--s-1);
}
/* Таблица шире экрана листается в своей рамке, а не вместе со страницей.
   Пока вбок ехала страница, уезжало всё разом — шапка раздела, поиск,
   фишки категорий, — и слева оставалось пустое поле шириной в монитор.
   Тень у правого края говорит, что справа ещё есть: слой local едет с
   содержимым и закрывает её на самом краю, слой scroll стоит на месте. */
.tblscroll {
  overflow: auto; max-width: 100%;
  border-radius: var(--radius);
  /* Высоту ставит разметка: над таблицей то одна строка отборов, то три,
     и зашитое сюда число врало бы через раз. */
  background:
    linear-gradient(to left, var(--surface), transparent) right center / 24px 100% no-repeat local,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .13), transparent)
      right center / 10px 100% no-repeat scroll;
}
/* Шапка таблицы стоит на месте, пока едет всё остальное. Без неё на шестой
   сотне строк уже не помнишь, какой столбец что значит, а столбцов двадцать. */
.suptbl thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface);
  box-shadow: inset 0 -1px 0 var(--border);
}
.typechip--more { border-style: dashed; color: var(--text-dim); }
