/* Палитра единая с tab.wilde.tech (legal/WildeTech/Tab/build.py) */
:root{
  --ink:#1a1c20; --muted:#5c6470; --line:#e3e6ea; --bg:#fff;
  --accent:#2a5bd7; --soft:#f6f8fa;
  --button-bg:#2a5bd7; --button-fg:#fff;
  --green:#177245; --green-bg:#e9f6ef;
  --red:#b3261e; --red-bg:#fdecea;
  --blue-bg:#eaf1fd;
  --maxw:960px;
  /* Производные светлой темы, вынесенные из правил: в тёмной у них другие
     значения, а литерал в правиле переключаться не умеет. */
  --ink-soft:#33373d; --code-bg:#eceff3; --backdrop:rgba(26,28,32,.45);
  --red-line:#f0b5b1; --green-line:#bfe3cf; --blue-line:#c5d7f7; --warn-ink:#8a6d1f;
  /* Подложка предупреждения: разбел тех же чернил. ⚠️ Заводится ПАРОЙ со
     строкой тёмной темы ниже — цвет, объявленный в одной теме, во второй
     не переключается и остаётся светлым пятном на тёмном. */
  --warn-bg:#fdf6e0; --warn-line:#e6d29a;
  /* Теги авторства: чернила спокойные, фон — их разбел. */
  --tag-green:#177245; --tag-green-bg:#e4f0e9;
  --tag-wine:#8c2f39;  --tag-wine-bg:#f6e7e9;
  --tag-blue:#2a4b8d;  --tag-blue-bg:#e6ecf7;
  --tag-ochre:#8a6d1f; --tag-ochre-bg:#f4eedd;
  --tag-ours:#5c6470;  --tag-ours-bg:#eceff3;
}

/* Тёмная тема кабинета.
 *
 * ⭐ Ставится тем же атрибутом `data-theme` и тем же ключом `wg:theme`, что и
 * тема полотна графа. Одна тема на весь экран: тёмное полотно внутри светлой
 * страницы (как было до 09.08) — это не «изолированный компонент», а разъезд,
 * который видит заказчик.
 *
 * Фон не чистый чёрный и не почти-чёрный — по той же причине, что и в движке:
 * на очень тёмном светлый текст даёт ореол вокруг букв, а под #07090c нет
 * ступени, из которой строить иерархию поверхностей. */
[data-theme="dark"]{
  --ink:#e6eef4; --muted:#93a3b0; --line:#26313c; --bg:#151b22;
  --accent:#5b9bff; --soft:#0d1116;
  --button-bg:#5b9bff; --button-fg:#151b22;
  --green:#57c68d; --green-bg:#12241b; --green-line:#1f4432;
  --red:#f08a80; --red-bg:#2a1614; --red-line:#5b2a25;
  --blue-bg:#132132; --blue-line:#28466e;
  --ink-soft:#c2ced8; --code-bg:#1b2229; --backdrop:rgba(5,8,11,.62);
  --warn-ink:#d9a441; --warn-bg:#2a2416; --warn-line:#5c4a1f;
  /* ⚠️ В тёмной теме чернила ОСВЕТЛЯЮТСЯ, а фон гасится: тот же бордовый на
     тёмном фоне читался бы как грязное пятно, а не как спокойный тон. */
  --tag-green:#7cc79b; --tag-green-bg:#16281f;
  --tag-wine:#d98b93;  --tag-wine-bg:#2b1a1d;
  --tag-blue:#8fb0e8;  --tag-blue-bg:#182234;
  --tag-ochre:#d9bd72; --tag-ochre-bg:#292317;
  --tag-ours:#93a3b0;  --tag-ours-bg:#1b2229;
}
*{box-sizing:border-box}
body{
  margin:0; background:var(--soft); color:var(--ink);
  font:16px/1.55 -apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
a{color:var(--accent)}
h1{font-size:26px;margin:0 0 18px}
h2{font-size:18px;margin:34px 0 12px}
.quiet{color:var(--muted);font-weight:400}

/* Шапка */
.topbar{background:var(--bg);border-bottom:1px solid var(--line)}
.topbar-inner{
  max-width:none;margin:0 auto;padding:14px 24px;
  display:flex;align-items:center;gap:20px;flex-wrap:wrap;
}
.brand{font-weight:800;font-size:20px;color:var(--accent);text-decoration:none;letter-spacing:.02em}
.brand.big{font-size:32px}
/* ⛔ Шапка ОДИНАКОВА на всех экранах, и это правило, а не оформление. Раньше на
   полноэкранных страницах (полотно графа, календарь) она шла во всю ширину, а на
   обычных стояла в колонке 960 — меню прыгало с места на место при переходе
   между разделами, и один и тот же орган выглядел разным (замечание владельца
   17.08.2026). Теперь во всю ширину везде; у полноэкранных остаётся только
   компактная ВЫСОТА — там каждый пиксель отнят у холста, а по горизонтали
   ничего не меняется.

   ⚠️ `flex:0 1 auto`, а НЕ `flex:1`. При `flex:1` базис равен нулю, меню
   растягивается на всё свободное место и выдавливает блок «кто вошёл» на вторую
   строку — шапка занимала две строки при любом числе пунктов. Одного
   `flex-basis:auto` при этом мало: расти заставляет `flex-grow`, поэтому он снят
   вовсе, а «кто вошёл» прижимается вправо сам. Проверено замером, а не на глаз:
   первая попытка поменяла только базис и не изменила ничего. */
.topbar nav{display:flex;gap:16px;flex:0 1 auto;min-width:0;flex-wrap:wrap}
.topbar nav a{color:var(--muted);text-decoration:none;padding:4px 2px;
  border-bottom:2px solid transparent;white-space:nowrap}
.topbar nav a.on{color:var(--ink);border-bottom-color:var(--accent);font-weight:600}
.topbar nav a:hover{color:var(--ink)}
.who{display:flex;gap:14px;align-items:center;font-size:14px;color:var(--muted);margin-left:auto}

/* Второй уровень навигации: подпункты модуля (Настройки, Клиенты).
   ⭐ Тише верхней строки НАМЕРЕННО — меньше кегль, подложка `--soft`, выделение
   не подчёркиванием, а заливкой. Два одинаково громких ряда ссылок читаются как
   один длинный список, и вопрос «где я» становится сложнее, а не проще.
   ⚠️ Все цвета — из палитры. Литерал по месту не подчиняется теме: на этом уже
   горели белые плашки командного контура в тёмной теме (16.08.2026). */
.subnav{background:var(--soft);border-bottom:1px solid var(--line)}
.subnav-inner{
  max-width:var(--maxw);margin:0 auto;padding:8px 20px;
  display:flex;gap:6px;flex-wrap:wrap;
}
.subnav a{
  color:var(--muted);text-decoration:none;font-size:14px;
  padding:5px 12px;border-radius:8px;
}
.subnav a:hover{color:var(--ink);background:var(--bg)}
.subnav a.on{color:var(--ink);background:var(--bg);font-weight:600;
  box-shadow:inset 0 0 0 1px var(--line)}

/* Профиль: фото и то, что рядом с ним */
.profile-photo{display:flex;gap:20px;align-items:flex-start;flex-wrap:wrap}
.avatar-lg{
  width:96px;height:96px;border-radius:50%;object-fit:cover;
  border:1px solid var(--line);flex:none;
}
/* Инициалы вместо фото: кружок тем же размером, чтобы страница не прыгала,
   когда фото появится. */
.avatar-empty{
  display:flex;align-items:center;justify-content:center;
  background:var(--soft);color:var(--muted);
  font-size:32px;font-weight:700;letter-spacing:.02em;
}

/* Контент */
.page{max-width:var(--maxw);margin:0 auto;padding:32px 20px 56px}
.foot{max-width:var(--maxw);margin:0 auto;padding:18px 20px 40px;color:var(--muted);font-size:13.5px;border-top:1px solid var(--line)}

/* Карточки баланса */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px;margin:0 0 8px}
.card{background:var(--bg);border:1px solid var(--line);border-radius:12px;padding:18px 20px}
.card-warn{border-color:var(--red-line);background:var(--red-bg)}
.card-label{font-size:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin-bottom:8px}
.card-value{font-size:28px;font-weight:700}
.card-warn .card-value{color:var(--red)}
.card-note{font-size:13.5px;color:var(--muted);margin-top:6px}
.card-warn .card-note{color:var(--red)}

/* Таблицы */
table{width:100%;border-collapse:collapse;background:var(--bg);border:1px solid var(--line);border-radius:12px;overflow:hidden;font-size:15px}
th,td{padding:11px 14px;text-align:left;border-bottom:1px solid var(--line)}
thead th{background:var(--soft);font-size:13px;letter-spacing:.05em;text-transform:uppercase;color:var(--muted)}
tbody tr:last-child td{border-bottom:none}
.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.pos{color:var(--green);font-weight:600}
.neg{color:var(--ink)}

/* Исполнитель под описанием операции */
.performer{font-size:13.5px;color:var(--muted);margin-top:2px}
.nowrap{white-space:nowrap}

/* Кликабельные строки с деталями + попап */
tr.clickable{cursor:pointer}
tr.clickable:hover td{background:var(--soft)}
.more-hint{font-size:12.5px;color:var(--accent);border-bottom:1px dashed var(--accent);white-space:nowrap}
dialog#op-dialog{
  border:1px solid var(--line);border-radius:14px;padding:22px 26px;
  max-width:560px;width:calc(100vw - 40px);background:var(--bg);color:var(--ink);
}
dialog#op-dialog::backdrop{background:var(--backdrop)}
.dialog-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:10px}
.dialog-head h3{margin:0;font-size:17px}
#op-dialog-close{
  border:none;background:none;font-size:26px;line-height:1;color:var(--muted);
  cursor:pointer;padding:0 2px;
}
#op-dialog-close:hover{color:var(--ink)}
#op-dialog-body{max-height:60vh;overflow-y:auto}
/* Подтверждение действия клиента (заявка из витрины). Цвета — из тех же
   переменных темы, поэтому блок остаётся читаемым и в тёмной. */
.ok-note{border:1px solid var(--green-line);border-radius:10px;background:var(--green-bg);
         color:var(--green);padding:12px 14px;margin:0 0 14px;font-weight:600}
.msg{border:1px solid var(--line);border-radius:10px;background:var(--soft);padding:12px 14px;margin:0 0 10px}
.msg:last-child{margin-bottom:0}
.msg-head{font-size:13px;font-weight:600;color:var(--accent);margin-bottom:6px}
.msg-body{font-size:14.5px;color:var(--ink-soft);line-height:1.5;overflow-wrap:break-word}
.msg-body p{margin:0 0 10px}
.msg-body p:last-child, .msg-body ul:last-child, .msg-body ol:last-child{margin-bottom:0}
.msg-body ul, .msg-body ol{margin:0 0 10px; padding-left:22px}
.msg-body li{margin:3px 0}
.msg-body code{background:var(--code-bg);border-radius:4px;padding:1px 5px;font-size:13px;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.msg-body pre{background:var(--code-bg);border-radius:8px;padding:10px 12px;overflow-x:auto}
.msg-body pre code{background:none;padding:0}
.msg-body blockquote{margin:0 0 10px;padding:6px 12px;border-left:3px solid var(--line);color:var(--muted)}
.msg-body a{color:var(--accent)}
.msg-body h1,.msg-body h2,.msg-body h3,.msg-body h4{font-size:15px;margin:12px 0 6px}

/* Теги статусов */
.tag{display:inline-block;padding:2px 10px;border-radius:999px;font-size:13px;background:var(--soft);border:1px solid var(--line);color:var(--muted);white-space:nowrap}
.tag-green{background:var(--green-bg);border-color:var(--green-line);color:var(--green)}
.tag-blue{background:var(--blue-bg);border-color:var(--blue-line);color:var(--accent)}

/* Логин */
.center-page{display:flex;min-height:100vh;align-items:center;justify-content:center;background:var(--soft)}
.login-card{background:var(--bg);border:1px solid var(--line);border-radius:14px;padding:40px 44px;max-width:440px;margin:20px;text-align:center}
.login-card h1{font-size:22px;margin:14px 0 10px}
.login-card p{color:var(--muted);font-size:15px}
.err{color:var(--red)}
.sso{margin:18px 0 6px}
.sso-button{display:inline-block;padding:11px 22px;border-radius:9px;background:var(--button-bg);color:var(--button-fg);text-decoration:none;font-size:15px}
.sso-button:hover{filter:brightness(1.08)}
/* Согласие у двери (152-ФЗ): мельче основного текста, но не серее ссылки. */
.login-card .consent{font-size:13px;margin-top:8px}
.login-card .consent a{color:var(--accent)}
/* Политика — та же карточка, но текстом влево и шире. */
.prose-card{max-width:760px;text-align:left}
.prose-card h1{font-size:20px}
.prose-card h2{font-size:16px;margin:22px 0 6px}
.prose-card p,.prose-card li{color:var(--ink);font-size:14.5px;line-height:1.5}
.prose-card ul{padding-left:20px}
/* Уведомление о cookie: одна полоска внизу, поверх содержимого, до нажатия
   «Понятно». ⛔ `[hidden]` перекрывается своим `display:flex`, поэтому правило
   на скрытое состояние стоит рядом (tests/test_hidden_not_overridden.py). */
.cookie-note{position:fixed;left:16px;right:16px;bottom:16px;z-index:60;max-width:680px;margin:0 auto;
  display:flex;gap:14px;align-items:center;justify-content:space-between;padding:12px 16px;
  border:1px solid var(--line);border-radius:12px;background:var(--bg);color:var(--ink);
  font-size:14px;box-shadow:0 8px 24px rgba(0,0,0,.12)}
.cookie-note[hidden]{display:none}
.cookie-note a{color:var(--accent)}
.cookie-note button{flex:none;padding:8px 14px;border-radius:8px;border:1px solid var(--line);
  background:var(--soft);color:var(--ink);cursor:pointer}

@media (max-width:640px){
  .card-value{font-size:24px}
  table{font-size:14px}
  th,td{padding:9px 10px}
}

/* --- Графовая система проектов (docs/PROJECT-GRAPH-DESIGN.md) --- */
.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13.5px;color:var(--muted);margin:0 0 18px}
.legend-ready{border:2px solid var(--accent);border-radius:6px;padding:0 6px;color:var(--ink)}
.layer-note{margin:-8px 0 10px;font-size:14px}
.stages td{vertical-align:top}
.stages .st{width:180px}
.stage-name{font-weight:600}
.stages .value{color:var(--muted);font-size:14.5px;width:38%}
.milestone-badge{font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  background:var(--blue-bg);color:var(--accent);border-radius:4px;padding:1px 6px;vertical-align:middle}
.blocked{font-size:13.5px;color:var(--warn-ink);margin-top:4px}
.ready{font-size:13.5px;color:var(--accent);margin-top:4px}
tr.av-done .stage-name{color:var(--muted)}

/* Полотно графа. Фон и кромку берём из токенов движка (`--wg-void`,
   `--wg-panel-edge`): полотно и кабинет живут в одной теме, и подложка под ним
   обязана переключаться вместе с ним — иначе в светлой теме вокруг светлого
   полотна остаётся чёрная рамка. */
.graph-stage{height:min(84vh,900px);border-radius:14px;overflow:hidden;
  border:1px solid var(--wg-panel-edge,#0b1015);background:var(--wg-void,#0d1116)}
/* Мобильный список движка прокручивается внутри окна: большой проект не
   растягивает страницу на десятки тысяч пикселей. */
.only-narrow{display:none}
@media (max-width:719px){
  .graph-stage{height:auto;max-height:70vh;overflow:auto}
  /* Пояснение к виду: на узком экране показывается список, и текст про колонки,
     перетаскивание и клик по узлу описывал бы интерфейс, которого там нет. */
  .only-wide{display:none}
  .only-narrow{display:block}
}
.graph-card{background:var(--bg);border:1px solid var(--line);border-radius:12px;padding:16px 18px;margin-top:14px}
.graph-card h3{margin:0 0 4px;font-size:17px}
.value-block{margin-top:8px;color:var(--ink)}
.btn{background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:8px 14px;
  font:inherit;font-size:14px;color:var(--ink);cursor:pointer}
.btn:hover{border-color:var(--accent);color:var(--accent)}
.chip{display:inline-block;border:1px solid var(--line);border-radius:999px;padding:3px 12px;
  margin-right:8px;font-size:14px;text-decoration:none;color:var(--muted)}
.chip.on{border-color:var(--accent);color:var(--accent);font-weight:600}

/* Замечание: заголовок, теги, ключ. Теги заменили коды реестра (A4, H12) —
   решение владельца 20.08.2026. ⚠️ Тег НЕ переиспользует `.chip`: chip у нас
   переключатель вида, на который жмут, и одинаковый вид у метки и у кнопки
   учил бы жать на то, что не нажимается. Тег плоский, без рамки и мельче.
   Ключ тикета уводим вправо и приглушаем: он служебный — нужен, чтобы
   сослаться в переписке, а не чтобы читать список. */
.incident-actions{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:10px;align-items:start}
.incident-return summary{display:inline-block;list-style:none}
.incident-return summary::-webkit-details-marker{display:none}
.incident-return summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.incident-return form{margin-top:10px}
@media(max-width:480px){.incident-actions{grid-template-columns:1fr}}
.incident-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin:0}
.incident-key{flex:none;font-size:13px;color:var(--muted);text-decoration:none;white-space:nowrap}
.incident-key:hover{color:var(--accent)}
.incident-tags{margin:2px 0 0}
/* ⭐ Тег СТАЛ органом управления (решение владельца 24.08.2026: «давай сделаем
   теги кликабельными для фильтра»). Прежняя редакция намеренно держала его
   плоским, чтобы метка не выглядела кнопкой; теперь по метке действительно
   жмут, и вид обязан это признать — курсор, наведение, фокус с клавиатуры.
   ⚠️ Подчёркивания нет: тег остаётся плашкой, а не ссылкой в тексте, — от
   ссылок его отличает форма, а от кнопок то, что он ведёт к отбору, а не
   выполняет действие. */
.tag{display:inline-block;background:var(--line);border-radius:6px;padding:1px 8px;
  margin-right:6px;font-size:12.5px;color:var(--muted);text-decoration:none}
a.tag{cursor:pointer}
a.tag:hover,a.tag:focus-visible{filter:brightness(.94);color:var(--ink-soft)}
a.tag.on{outline:2px solid currentColor;outline-offset:1px}

/* Цвета тегов авторства (решение владельца 24.08.2026). ⭐ Спокойные, «земляные»:
   тег стоит РЯДОМ с названием замечания и не должен спорить с ним за внимание —
   это подпись, а не значок состояния. Поэтому насыщенность ниже, чем у статусных
   цветов (--green/--red), а фон — их же оттенок в разбеле.
   ⚠️ Пара «фон + чернила» задаётся для каждой темы отдельно: в тёмной те же
   бордовый и охра на светлом фоне дали бы грязь, а не спокойный тон.
   ⛔ Тема (`склады`, `дашборд`) цвета НЕ получает: цветной значит «человек», и
   если покрасить всё, признак перестаёт что-либо отличать. */
.tag--green{background:var(--tag-green-bg);color:var(--tag-green)}
.tag--wine{background:var(--tag-wine-bg);color:var(--tag-wine)}
.tag--blue{background:var(--tag-blue-bg);color:var(--tag-blue)}
.tag--ochre{background:var(--tag-ochre-bg);color:var(--tag-ochre)}
.tag--ours{background:var(--tag-ours-bg);color:var(--tag-ours)}
/* ⚠️ Чип панели отбора носит тот же цвет ТЕКСТОМ и рамкой, но без заливки:
   залитый чип рядом с залитой меткой читался бы как второй тег, а не как
   переключатель. Цвет говорит «это тот же человек», форма — «это орган». */
.chip.tag--green{color:var(--tag-green);border-color:var(--tag-green-bg);background:none}
.chip.tag--wine{color:var(--tag-wine);border-color:var(--tag-wine-bg);background:none}
.chip.tag--blue{color:var(--tag-blue);border-color:var(--tag-blue-bg);background:none}
.chip.tag--ochre{color:var(--tag-ochre);border-color:var(--tag-ochre-bg);background:none}
.chip.tag--ours{color:var(--tag-ours);border-color:var(--tag-ours-bg);background:none}
.chip.tag--green.on,.chip.tag--wine.on,.chip.tag--blue.on,
.chip.tag--ochre.on,.chip.tag--ours.on{border-color:currentColor;font-weight:600}
.problems{background:var(--red-bg);border:1px solid var(--red-line);border-radius:10px;padding:12px 14px 12px 32px;margin:0}
.problems li{margin:4px 0;font-size:14.5px}
.ok-line{color:var(--green);background:var(--green-bg);border:1px solid var(--green-line);
  border-radius:10px;padding:10px 14px;margin:0}

/* Кнопка темы в шапке. Иконочная, без подписи: место в шапке дорогое, а смысл
   договаривает title. Размер — не меньше 32px: это цель для пальца, а шапка
   живёт и на телефоне. */
.theme-toggle{
  border:1px solid var(--line);background:var(--bg);color:var(--muted);
  border-radius:8px;width:32px;height:32px;line-height:1;font-size:15px;
  cursor:pointer;display:inline-flex;align-items:center;justify-content:center;
}
.theme-toggle:hover{border-color:var(--accent);color:var(--accent)}

/* Переключатель видов внутри вкладки. Отделён от верхней навигации намеренно:
   вкладка отвечает на вопрос «о чём раздел», вид — «как на это смотреть», и
   слияние этих двух уровней вернуло бы граф на первый экран. */
.views{margin:-6px 0 18px}

/* Формы админки графа. Поля в ряд, но переносятся: админку открывают и с ноута,
   и с большого монитора, а горизонтальная прокрутка формы — гарантированный
   способ потерять кнопку «Сохранить». */
.form-row{display:flex;gap:16px;flex-wrap:wrap;align-items:flex-end}
/* Значение, которое считается, а не правится: стоит в ряду органов правки и
   обязано быть на них не похоже — иначе читается как сломанный список. */
.field-static{display:block;padding:6px 0;font-size:15px;color:var(--ink)}
.graph-card label{display:inline-block;font-size:14px;color:var(--muted)}
.graph-card input[type=text],.graph-card input[type=date],
.graph-card select,.graph-card textarea{
  font:inherit;font-size:15px;color:var(--ink);background:var(--bg);
  border:1px solid var(--line);border-radius:8px;padding:7px 10px;margin-top:3px;
}
.graph-card textarea{width:100%;resize:vertical}
.graph-card input:focus,.graph-card select:focus,.graph-card textarea:focus{
  outline:none;border-color:var(--accent)
}
/* Полоса «данные демонстрационные». Не красная тревога, но и не мелкий шрифт:
   цифра без оговорки читается как факт, а суммы в кабинете сотрудника пока
   выдуманы (провайдера выплат ещё нет). */
.demo-banner{margin:0 0 18px;padding:10px 14px;border-radius:10px;font-size:14px;
  color:var(--ink);background:color-mix(in srgb,var(--accent) 10%,transparent);
  border:1px solid color-mix(in srgb,var(--accent) 35%,transparent)}

/* ============================================ Оболочка полотна (админка графа)
   ⭐ Полотно — сама страница, а не блок на ней. Раньше граф жил в окне 84vh
   посреди колонки 960px: инструмент, где работа занимает четверть экрана,
   заставляет листать вместо того, чтобы смотреть. Здесь холст занимает всё, а
   органы управления уступают ему место и убираются одной кнопкой. */
body.full{height:100vh;display:flex;flex-direction:column;overflow:hidden}
body.full .topbar{flex:none}
/* ⚠️ `--side-edge` — где на экране проходит ПРАВЫЙ край панели. Считать его
   заново в каждом правиле нельзя: стоило добавить ленту слева, как ручка
   ширины и стрелка сворачивания, отсчитанные от `--side-width`, оказались
   посреди панели, а не на её границе (замечание владельца 16.08). */
:root{--side-width:320px;--rail-width:57px;--side-edge:calc(var(--rail-width) + var(--side-width))}
main.shell{
  flex:1;min-height:0;display:grid;
  grid-template-columns:var(--rail-width) var(--side-width) minmax(0,1fr);
  max-width:none;margin:0;padding:0;position:relative;
}
/* Свёрнута ПАНЕЛЬ раздела, а лента остаётся: с неё же её и открывают. */
body.side-hidden main.shell{grid-template-columns:var(--rail-width) 0 minmax(0,1fr)}
/* Пока тянут границу — курсор один на весь экран, а выделение текста выключено:
   иначе перетаскивание превращается в выделение панели. */
body.is-resizing{cursor:col-resize;user-select:none}
body.is-resizing .shell__side{transition:none}

.shell__side{
  min-width:0;overflow:hidden;border-right:1px solid var(--line);background:var(--bg);
  transition:opacity .15s ease;
}
body.side-hidden .shell__side{opacity:0;pointer-events:none}
.shell__scroll{height:100%;overflow-y:auto;padding:18px 18px 40px}

/* ⚠️ Кнопка стоит СНАРУЖИ панели: спрятанная вместе с ней, она не смогла бы её
   вернуть. При свёрнутой панели это единственный след интерфейса на экране. */
/* ⚠️ Кнопка сидит на ГРАНИЦЕ панели по вертикали посередине, а не в верхнем
   углу холста: сверху стоит панель управления полотном («Разложить», тема, «?»),
   и кнопка под ней не кликалась вовсе — Playwright показал, что клик
   перехватывает `.wg-controls`. Место посередине границы — то же решение, что
   у VS Code и Miro: оно свободно при любом содержимом. */
.shell__toggle{
  position:absolute;top:50%;left:var(--side-edge);z-index:6;transform:translate(-50%,-50%);
  width:26px;height:26px;border-radius:50%;cursor:pointer;
  border:1px solid var(--line);background:var(--bg);color:var(--muted);
  font-size:13px;line-height:1;padding:0;
  box-shadow:0 2px 8px -4px var(--backdrop);
}
body.side-hidden .shell__toggle{left:16px;transform:translateY(-50%)}
.shell__toggle:hover{color:var(--ink)}

.shell__canvas{position:relative;min-width:0;min-height:0}
.graph-stage--full{height:100%;border:none;border-radius:0}
.canvas-flash{
  position:absolute;top:12px;left:50%;transform:translateX(-50%);z-index:4;
  margin:0;padding:8px 14px;border-radius:9px;background:var(--bg);
  border:1px solid var(--line);font-size:14px;max-width:70%;
}

/* Окно публикации: та же форма, что была в панели, перенесённая оболочкой
   сюда. ⛔ Модальным сделано намеренно: публикация — единственное действие
   админки, которое видит заказчик, и спрашивать «релиз или правка» вполглаза,
   не отрываясь от холста, тут не надо. */
.publish-modal{
  position:fixed;inset:0;z-index:40;display:flex;align-items:center;
  justify-content:center;background:rgba(12,14,18,.45);
}
.publish-modal[hidden]{display:none}
.publish-modal__card{
  position:relative;background:var(--bg);border:1px solid var(--line);
  border-radius:12px;padding:22px 24px;width:min(420px,92vw);
  box-shadow:0 18px 48px rgba(0,0,0,.28);
}
.publish-modal__card .side-block{margin:0}
.publish-modal__close{
  position:absolute;top:8px;right:10px;border:0;background:none;
  color:var(--muted);font-size:22px;line-height:1;cursor:pointer;padding:4px;
}
.publish-modal__close:hover{color:var(--ink)}

/* ⭐ Лента разделов — постоянная вертикальная полоса иконок у левого края.
   ⛔ Она НЕ сворачивается, и это её главное свойство: возврат к панели встроен
   в элемент, который всегда на экране. Четыре раза подряд ломалась ровно
   обратная конструкция — «свернули всё, вернуть плавающей кнопкой», и кнопка
   каждый раз оказывалась под органами движка. */
.rail{
  grid-row:1;display:flex;flex-direction:column;gap:2px;
  padding:8px 6px;border-right:1px solid var(--line);background:var(--bg);
  overflow:visible;z-index:7;
}
.rail__item{
  position:relative;display:flex;align-items:center;justify-content:center;
  width:44px;height:44px;flex:0 0 auto;
  border:1px solid transparent;border-radius:10px;background:none;
  color:var(--muted);cursor:pointer;padding:0;
}
.rail__item:hover{color:var(--ink);background:var(--soft)}
.rail__item.on{color:var(--accent);background:var(--blue-bg);border-color:var(--blue-line)}
.rail__item[hidden]{display:none}

/* Значок растёт при наведении. ⚠️ Через transform, а не через размер шрифта:
   размер шрифта пересчитывает раскладку всей ленты, transform — не трогает
   ничего, кроме самой иконки. */
.rail__icon{font-size:19px;line-height:1;transition:transform .16s ease-out}
.rail__item:hover .rail__icon,
.rail__item:focus-visible .rail__icon{transform:scale(1.32)}

/* Подпись выезжает вправо от значка. Поверх панели, а не в потоке: в потоке
   она раздвинула бы ленту, и та дёргалась бы под курсором. */
.rail__label{
  position:absolute;left:calc(100% + 8px);white-space:nowrap;
  padding:5px 10px;border-radius:8px;border:1px solid var(--line);
  background:var(--bg);color:var(--ink);font-size:13px;
  box-shadow:0 4px 14px -8px var(--backdrop);
  opacity:0;transform:translateX(-6px);pointer-events:none;
  transition:opacity .16s ease-out, transform .16s ease-out;
}
.rail__item:hover .rail__label,
.rail__item:focus-visible .rail__label{opacity:1;transform:translateX(0)}

/* ⚠️ Кому система выключила анимацию по медицинским показаниям — выключаем и
   здесь: правило то же, что у движения на полотне. */
@media (prefers-reduced-motion: reduce){
  .rail__icon,.rail__label{transition:none}
  .rail__item:hover .rail__icon{transform:none}
}

.side-section[hidden]{display:none}

/* Выбор «что добавить»: три кнопки в ряд. */
.add-choice{display:flex;gap:4px}
.add-choice__btn{
  flex:1;padding:7px 8px;border:1px solid var(--line);border-radius:8px;
  background:var(--bg);color:var(--muted);font:inherit;font-size:14px;cursor:pointer;
}
.add-choice__btn:hover{color:var(--ink)}
.add-choice__btn.on{background:var(--blue-bg);border-color:var(--blue-line);
  color:var(--accent);font-weight:600}
.add-pane[hidden]{display:none}

/* Список полей: перетаскиваемые строки. */
.lane-list{list-style:none;margin:0 0 10px;padding:0}
.lane-list__item{
  display:flex;align-items:center;gap:8px;padding:7px 8px;margin:0 0 3px;
  border:1px solid var(--line);border-radius:8px;background:var(--bg);
}
.lane-list__item.is-dragged{opacity:.4}
/* ⚠️ Цель показывается ЛИНИЕЙ сверху, а не подсветкой всей строки: подсветка
   отвечает «эта строка», а вопрос при перетаскивании — «между какими». */
.lane-list__item.is-over{box-shadow:0 -2px 0 0 var(--accent)}
.lane-list__grip{cursor:grab;color:var(--muted);font-size:13px;line-height:1}
.lane-list__item:active .lane-list__grip{cursor:grabbing}
.lane-list__name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-size:14.5px}
.lane-list__drop{
  border:1px solid transparent;border-radius:6px;background:none;color:var(--muted);
  width:22px;height:22px;line-height:1;font-size:15px;cursor:pointer;padding:0;flex:0 0 auto;
}
.lane-list__drop:hover{color:var(--red);border-color:var(--red-line)}
.lane-list__hint{margin-top:0}

/* Секции панели */
.side-block{margin:0 0 22px}
.side-title{font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);margin:0 0 8px}
.side-count{color:var(--muted);font-weight:400;letter-spacing:0;text-transform:none}
.side-item{
  display:block;width:100%;text-align:left;padding:6px 9px;margin:0 0 2px;
  border:1px solid transparent;border-radius:8px;background:none;cursor:pointer;
  color:var(--ink);font:inherit;font-size:14.5px;text-decoration:none;
}
.side-item:hover{background:var(--soft)}
.side-item.on{background:var(--blue-bg);border-color:var(--blue-line);color:var(--accent);font-weight:600}
/* Срез без источника: виден и кликается (объясняет причину), но читается как
   недоступный. ⚠️ Не opacity: она гасит и подпись «нет источника» до нечитаемого,
   а объяснить недоступность нечитаемым словом — то же самое, что скрыть пункт. */
.side-item.off{color:var(--muted);cursor:help}
.side-item.off:hover{background:var(--soft)}
.side-item__tag{margin-left:7px;font-size:11px;letter-spacing:.04em;
  color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:1px 7px;
  white-space:nowrap}
.side-hint{margin:2px 0 8px;padding:0 9px}
.side-project{font-size:15px;font-weight:600;line-height:1.35;margin:0 0 8px}
.side-note{margin:8px 0 0;font-size:13px;color:var(--muted);line-height:1.45}
.side-ok{margin:0;font-size:13.5px;color:var(--green)}
.side-input{width:100%;padding:7px 10px;border:1px solid var(--line);border-radius:8px;
  background:var(--bg);color:var(--ink);font:inherit;font-size:14px}
/* Поиск и кнопка отбора — один ряд: разные вопросы, но одно место, где их
   задают. Кнопка не растягивается, поле забирает остаток. */
.side-search-row{display:flex;gap:6px;align-items:stretch}
.side-search-row .side-input{flex:1;min-width:0}
.icon-btn{position:relative;flex:none;display:inline-flex;align-items:center;
  justify-content:center;width:34px;border:1px solid var(--line);border-radius:8px;
  background:var(--bg);color:var(--muted);cursor:pointer;padding:0}
.icon-btn:hover,.icon-btn[aria-expanded="true"]{border-color:var(--accent);color:var(--accent)}
/* ⚠️ Счётчик действующего отбора виден и при СВЁРНУТОЙ панели: иначе полотно с
   половиной узлов читается как потеря данных, а не как включённый фильтр. */
.icon-btn__count{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;
  padding:0 4px;border-radius:999px;background:var(--button-bg);color:var(--button-fg);
  font-size:11px;line-height:16px;text-align:center}
.side-filters{margin-top:8px}
.side-filters[hidden]{display:none}
.side-filter-group{margin:0 0 8px}
.side-filter-group__title{font-size:12px;color:var(--muted);margin:0 0 4px;
  text-transform:uppercase;letter-spacing:.04em}
.side-filter-group__chips{display:flex;flex-wrap:wrap;gap:6px}
.side-filters__reset{margin-top:2px;background:none;border:none;padding:0;
  color:var(--accent);font:inherit;font-size:13px;cursor:pointer}
.side-chip{
  padding:3px 9px;border-radius:999px;border:1px solid var(--line);background:var(--bg);
  color:var(--muted);font:inherit;font-size:12.5px;cursor:pointer;
}
.side-chip.on{background:var(--blue-bg);border-color:var(--blue-line);color:var(--accent)}
.side-problems{margin:0;padding-left:18px;font-size:13.5px;color:var(--warn-ink)}
.side-problems li{margin:0 0 5px}

/* Дерево работ */
.side-tree__lane{font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);margin:12px 0 5px}
.side-tree__lane:first-child{margin-top:0}
.side-tree__item{
  display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  padding:5px 9px;border:1px solid transparent;border-radius:8px;background:none;
  cursor:pointer;color:var(--ink);font:inherit;font-size:14px;line-height:1.35;
}
.side-tree__item:hover{background:var(--soft)}
.side-tree__item.on{background:var(--blue-bg);border-color:var(--blue-line)}
.side-tree__mark{width:7px;height:7px;flex:none;border-radius:50%;background:var(--accent)}
.side-tree__mark.is-blocked{background:none;border:1.5px solid var(--muted)}

.side-label{display:block;margin:0 0 10px;font-size:13px;color:var(--muted)}
.side-label select,.side-label input,.side-label textarea{
  display:block;width:100%;margin-top:4px;padding:6px 9px;border:1px solid var(--line);
  border-radius:8px;background:var(--bg);color:var(--ink);font:inherit;font-size:14px}

/* Узкий экран: панель поверх холста выдвижным ящиком — делить 390px на две
   колонки нечего, а на полотне там и так список. */
@media (max-width:719px){
  body.full{height:auto;overflow:visible}
  main.shell{display:block}
  .shell__side{border-right:none;border-bottom:1px solid var(--line)}
  .shell__scroll{height:auto}
  .shell__toggle{position:sticky;top:8px;left:auto;margin:8px;transform:none}
  body.side-hidden .shell__side{display:none}
}

/* Компактная шапка на полноэкранных страницах: на обычной странице она стоит
   один раз наверху и цена ей — ничто, а здесь каждый её пиксель отнят у холста.
   ⚠️ Пункты не переносятся: перенос делал шапку двухэтажной и съедал 10% высоты
   экрана (замер 1440×900). */
body.full .topbar-inner{padding:8px 24px}
/* ⚠️ Запрет переноса — только на широком экране. На узком он выпихивает пункты
   за край и даёт горизонтальную прокрутку всей страницы (замер 390px: строка
   навигации уезжала до 914px при окне 390). Там шапка переносится, как раньше. */

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

/* ⚠️ Прежде запрет переноса внутри пункта стоял ТОЛЬКО на полноэкранных
   страницах и только на широком экране — из-за «Графа проектов», который на
   узком выпихивал за край всю страницу. Пункт теперь называется «Проекты», и
   запрет переехал в общее правило: подпись одна и та же на всех экранах, а
   разное поведение на соседних страницах и было тем, что владелец увидел как
   «меню разное». На телефоне пункты теперь открываются отдельным списком. */
/* ⚠️ Длинное имя («Александр С. (аналитик данных, Senior)» — 363px) больше не
   правится здесь: оно укорачивается многоточием общим правилом в team.css, и
   потому не выталкивает ничего ни на широком экране, ни на телефоне. */


/* Ручка ширины: невидимая полоса на границе, курсор подсказывает, что её тянут.
   ⭐ Ширину задаёт человек: 320px хороши для коротких названий и тесны для
   длинных, а экраны у всех разные. Ширина запоминается, как раскладка узлов. */
.shell__resizer{
  position:absolute;top:0;bottom:0;left:var(--side-edge);width:9px;z-index:5;
  transform:translateX(-4px);cursor:col-resize;touch-action:none;
}
.shell__resizer::after{
  content:"";position:absolute;top:0;bottom:0;left:4px;width:1px;background:transparent;
  transition:background .15s ease;
}
.shell__resizer:hover::after,body.is-resizing .shell__resizer::after{background:var(--accent)}
body.side-hidden .shell__resizer{display:none}

/* Развернуть — словом и в углу. Значок посреди пустого поля не читается как
   «здесь что-то было»; угол — то место, где ищут навигацию. */
.shell__open{
  position:absolute;top:10px;left:12px;z-index:6;display:none;
  padding:6px 12px;border-radius:9px;cursor:pointer;
  border:1px solid var(--line);background:var(--bg);color:var(--ink);
  font:inherit;font-size:13.5px;box-shadow:0 4px 14px -8px var(--backdrop);
}
.shell__open:hover{border-color:var(--accent);color:var(--accent)}
body.side-hidden .shell__open{display:block}

/* ⛔ Кнопка «Панель» БОЛЬШЕ НЕ НУЖНА (16.08.2026): панель открывают щелчком по
   разделу в ленте, а лента с экрана не исчезает. Разметку оставляем ради пути
   БЕЗ JS — там лента ничего не переключает, и кнопка остаётся единственным
   способом вернуть панель.
   ⚠️ Раньше здесь стоял ещё и разворот сетки в одну колонку — он достался от
   мобильного правила, которое я поднял на все ширины, когда кнопка ещё была
   нужна. С лентой это ломало экран целиком: свёрнутая панель схлопывала сетку,
   и лента с холстом вставали друг под друга. Урок тот же, что и всегда: правило,
   перенесённое вместе со своей эпохой, переживает её и начинает вредить. */
body.side-hidden .shell__open{display:none}
body.side-hidden .shell__toggle{display:none}

/* ⭐ **Прокрутка ВЕЗДЕ тонкая и своя** (решение владельца 29.08.2026: «без полос,
   а если полосы появляются — красивые, в стиль»). Системная полоса на тёмной
   теме выглядит инородной светлой планкой поверх интерфейса.
   ⛔ Прежде так была оформлена ОДНА боковая панель (`.shell__scroll`), и каждое
   новое место прокрутки — диалог правки события, подсказка участников, карточка
   созвона, полотно графа — получало системную полосу. Вид, заведённый под одно
   место, не распространяется сам; поэтому он поднят на корень.
   ⚠️ `scrollbar-width` и `scrollbar-color` НАСЛЕДУЮТСЯ — их довольно задать на
   `html`. `::-webkit-scrollbar` не наследуется вовсе, ему нужен свой широкий
   селектор; отсюда два блока, а не один.
   ⚠️ Тонкая полоса не занимает места: она рисуется поверх, поэтому раскладку
   включение этого правила не сдвигает. */
html{
  scrollbar-width:thin;
  scrollbar-color:color-mix(in srgb,var(--muted) 34%,transparent) transparent;
}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{
  background:color-mix(in srgb,var(--muted) 34%,transparent);
  border-radius:999px;border:3px solid transparent;background-clip:content-box;
}
::-webkit-scrollbar-thumb:hover{background:var(--muted);background-clip:content-box}
/* Угол между горизонтальной и вертикальной полосой — иначе там остаётся
   серый квадрат системного вида. */
::-webkit-scrollbar-corner{background:transparent}
/* У боковой панели полоса ещё и проявляется под курсором: панель смотрят
   постоянно, и постоянная полоса на ней — лишний шум. */
.shell__scroll:hover::-webkit-scrollbar-thumb{
  background:color-mix(in srgb,var(--muted) 55%,transparent);background-clip:content-box}

/* ⚠️ Кнопки масштаба приходят от React Flow со своей светлой палитрой и на
   тёмной теме светятся белыми плашками поверх тёмного полотна. Красим их
   нашими токенами — теми же, что у панели, иначе на полотне окажется два
   разных интерфейса. */
.wg-root .react-flow__controls{box-shadow:0 6px 20px -12px var(--backdrop);border-radius:10px;overflow:hidden}
.wg-root .react-flow__controls-button{
  background:var(--bg);border-bottom:1px solid var(--line);color:var(--ink);
  width:28px;height:28px;
}
.wg-root .react-flow__controls-button:last-child{border-bottom:none}
.wg-root .react-flow__controls-button:hover{background:var(--soft)}
.wg-root .react-flow__controls-button svg{fill:currentColor}
/* Сообщение об ошибке прямо на холсте: рядом с тем местом, где ждали граф. */
.canvas-error{display:flex;flex-direction:column;align-items:flex-start;gap:10px;
  padding:24px;color:var(--muted);font-size:14.5px}
.canvas-error p{margin:0;max-width:40em}

/* Срез «Замечания» списком (graph-shell.js, режим list): группы по состояниям
   в области полотна. Свой скролл: у .graph-stage--full overflow:hidden. */
.flaw-list{height:100%;overflow-y:auto;padding:18px 22px 28px}
.flaw-list__note{margin:12px 0;color:var(--muted);max-width:46em}
.flaw-list__group{margin:0 0 20px;max-width:860px}
.flaw-list__head{margin:0 0 4px;font-size:15px;font-weight:600}
details.flaw-list__group>summary.flaw-list__head{cursor:pointer;color:var(--muted)}
.flaw-list__row{display:flex;align-items:baseline;gap:14px;
  padding:8px 10px;border-top:1px solid var(--line)}
.flaw-list__body{flex:1;min-width:0}
.flaw-list__title{font-size:14.5px}
.flaw-list__meta{font-size:12.5px;color:var(--muted);margin-top:2px}
.flaw-list__acts{display:flex;align-items:baseline;gap:10px;white-space:nowrap}
.flaw-list__key{font-size:12.5px}
.flaw-list__go{border:1px solid var(--line);background:none;border-radius:8px;
  padding:2px 10px;font:inherit;font-size:12.5px;color:var(--accent);cursor:pointer}
.flaw-list__go:hover{border-color:var(--accent)}
/* Главное действие панели размещения — заливкой; «убрать с полотна» —
   нейтральное: это отвязка, а не удаление, и красить его тревогой значит
   врать про цену нажатия (приёмка Kimi 24.08). */
.flaw-list__go--main{background:var(--button-bg);border-color:var(--button-bg);color:var(--button-fg)}
.flaw-list__go--main:hover{filter:brightness(1.08)}
.flaw-list__go--off{color:var(--muted)}
.flaw-list__go--off:hover{border-color:var(--muted)}
.flaw-list__off{font-size:12px;color:var(--muted);white-space:nowrap}

/* Раскрытие строки: заголовок — кнопка, стрелка поворачивается. */
.flaw-list__title{border:0;background:none;padding:0;font:inherit;color:inherit;
  text-align:left;cursor:pointer;display:block;width:100%}
.flaw-list__caret{display:inline-block;margin-right:6px;color:var(--muted);
  transition:transform .15s}
.flaw-list__row.is-open .flaw-list__caret{transform:rotate(90deg)}
.flaw-list__desc{margin:8px 0 0;font-size:13.5px;max-width:56em}
.flaw-list__desc.is-empty{color:var(--muted)}
.flaw-list__desc p{margin:0 0 8px}
.flaw-list__desc pre{overflow-x:auto;background:var(--code-bg);padding:8px;
  border-radius:8px}
.flaw-list__check{margin:8px 0 0;font-size:13px}
.flaw-list__zone{margin:6px 0 0;font-size:12.5px;color:var(--muted)}
.flaw-list__place{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin:10px 0 0}
.flaw-list__pick{max-width:340px;font:inherit;font-size:12.5px;
  color:var(--ink);background:var(--bg);border:1px solid var(--line);
  border-radius:8px;padding:3px 8px}
.flaw-list__verdict{margin:6px 0 0;font-size:12.5px;color:var(--muted)}
.flaw-list__verdict.is-err{color:var(--red)}
/* Ссылка правки у работы в дереве: появляется под курсором, не занимая места
   постоянно — список читают чаще, чем правят. */
.side-tree__item{position:relative;padding-right:26px}
.side-tree__edit{
  position:absolute;right:6px;top:50%;transform:translateY(-50%);
  opacity:0;color:var(--muted);text-decoration:none;font-size:13px;padding:2px 4px;
}
.side-tree__item:hover .side-tree__edit,
.side-tree__edit:focus{opacity:1;color:var(--accent)}

/* Крошка уровня: «← Завод данных · Раздел «Продажи»». Появляется только внутри
   уровня — на корневом срезе она была бы строкой, которая ничего не сообщает. */
/* ⚠️ Крошка стоит В ПОТОКЕ над холстом, а не поверх него. Поверх она легла
   ровно под панель управления полотна («Разложить», «Сбросить») и перестала
   кликаться — тем же способом, каким 10.08 пропала кнопка сворачивания. Полоса
   в потоке отнимает 34px и не может быть перекрыта ничем.
   Холст ужимается через grid-строку контейнера. */
.shell__canvas:has(.canvas-crumb){display:grid;grid-template-rows:auto minmax(0,1fr)}
.canvas-crumb{display:flex;align-items:center;gap:10px;padding:7px 16px;
  border-bottom:1px solid var(--line);background:var(--bg);font-size:13px}
.canvas-crumb__back{border:0;background:none;color:var(--accent);font:inherit;font-size:13px;
  cursor:pointer;padding:0}
.canvas-crumb__here{color:var(--muted)}


/* Подпункт среза: раздел внутри завода, схема внутри карты схем. Смещён и тише
   родителя — вложенность читается формой, а не только положением. */
/* ⛔ `display:block` у пункта СИЛЬНЕЕ браузерного правила для `hidden`:
   селектор по классу побеждает селектор по атрибуту из таблицы браузера, и
   свёрнутые подпункты оставались на экране. Возвращаем скрытие явно. */
.side-item[hidden] { display: none; }

.side-item--child {
  position: relative;
  padding-left: 26px;
  font-size: 13px;
  opacity: .92;
}

.side-item--child::before {
  content: "↳";
  position: absolute;
  left: 10px;
  opacity: .5;
}

/* Пояснение среза: что это за вид и как его читать. */
.side-view-note { margin-top: 10px; }

/* Полотно оплат (модуль «Оплаты», счётная модель).
 *
 * ⭐ Лента по датам, а не таблица: заказчик читает историю расчётов слева
 * направо и видит разрывы между работой и оплатой. Крайний правый узел —
 * работа, по которой счёта нет; он часть той же ленты, потому что вопрос
 * «а что дальше» возникает ровно там, где она обрывается.
 *
 * ⚠️ Ось нарисована псевдоэлементом на фиксированной высоте, и она обязана
 * совпасть с центром точек: 12px отступ + 18px строка даты + 10px зазор +
 * половина точки (7px) = 47px. Меняешь любую из этих величин — правь top. */
.tl-wrap{overflow-x:auto;margin:0 0 6px}
.tl{position:relative;display:flex;gap:26px;min-width:min-content;padding:12px 4px 10px}
.tl::before{content:"";position:absolute;left:8px;right:8px;top:46px;height:2px;background:var(--line)}
.tl-node{position:relative;flex:0 0 auto;width:128px;padding:0;border:0;background:none;
  color:inherit;font:inherit;cursor:pointer;text-align:center}
.tl-date{display:block;height:18px;line-height:18px;margin-bottom:10px;font-size:12.5px;
  letter-spacing:.05em;color:var(--muted);font-variant-numeric:tabular-nums}
.tl-dot{display:block;position:relative;z-index:1;width:14px;height:14px;margin:0 auto;
  border-radius:50%;background:var(--bg);border:2px solid var(--muted)}
.tl-sum{display:block;margin-top:12px;font-weight:700;font-variant-numeric:tabular-nums}
.tl-cap{display:block;margin-top:2px;font-size:12.5px;color:var(--muted)}
.tl-node.is-paid .tl-dot{background:var(--green);border-color:var(--green)}
.tl-node.is-overpaid .tl-dot{background:var(--accent);border-color:var(--accent)}
.tl-node.is-partial .tl-dot{border-color:var(--warn-ink)}
.tl-node.is-unpaid .tl-dot{border-color:var(--red)}
/* Пунктир у хвоста — не «оплошность», а «документа ещё нет». */
.tl-node.is-unbilled{width:170px}
.tl-node.is-unbilled .tl-dot{border-style:dashed}
.tl-node.is-active .tl-dot{box-shadow:0 0 0 4px var(--blue-bg)}
.tl-node:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:8px}

/* Карточка счёта: заголовок раскрывает состав и без скрипта. */
.inv{background:var(--bg);border:1px solid var(--line);border-radius:12px;margin:0 0 10px}
.inv>summary{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:14px 18px;
  cursor:pointer;list-style:none}
.inv>summary::-webkit-details-marker{display:none}
.inv>summary:hover{background:var(--soft);border-radius:12px}
.inv-num{font-weight:700}
.inv-sum{margin-left:auto;font-weight:700}
.inv-body{padding:0 18px 16px;border-top:1px solid var(--line)}
.inv-body h3{font-size:15px;margin:18px 0 8px}
.key{font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--muted)}
.tag-red{background:var(--red-bg);border-color:var(--red-line);color:var(--red)}
.tag-warn{color:var(--warn-ink);border-color:var(--warn-ink)}

/* Формы администрирования: строка «поле — поле — кнопка».
 * ⚠️ `align-items:end`, а не `center`: у полей есть подпись сверху, и по центру
 * кнопка встаёт выше строки ввода. */
.row{display:flex;gap:10px;align-items:end;flex-wrap:wrap;margin:10px 0}
.row label{display:flex;flex-direction:column;gap:4px;font-size:13.5px;color:var(--muted)}
.row input,.row select{padding:6px 9px;border:1px solid var(--line);border-radius:8px;
  background:var(--bg);color:var(--ink);font:inherit;min-width:170px}
.row button{padding:7px 14px;border:1px solid var(--line);border-radius:8px;
  background:var(--soft);color:var(--ink);font:inherit;cursor:pointer}
.row button:hover{background:var(--bg)}
/* Отмена счёта — необратимое для заказчика действие, и кнопка обязана
 * отличаться от «сохранить». */
.row button.danger{color:var(--red);border-color:var(--red-line);background:var(--red-bg)}
button.link{border:0;background:none;color:var(--accent);cursor:pointer;font:inherit;padding:0}

/* Полотно роадмапа оплат: блоки-этапы, разложенные РУКАМИ.
 *
 * ⚠️ Позиционирование абсолютное по координатам из базы. Никакого flex/grid:
 * раскладка здесь — смысл, который вложил владелец, а сетка переставила бы
 * блоки при первом же изменении ширины окна и стёрла бы этот смысл. */
.rm-wrap{overflow:auto;border:1px solid var(--line);border-radius:12px;
  background:var(--bg);margin:0 0 14px}
.rm{position:relative;min-width:100%;min-height:280px}
.rm-links{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.rm-links line{stroke:var(--line);stroke-width:2;marker-end:url(#rm-arrow)}
.rm-block{position:absolute;background:var(--bg);border:1px solid var(--line);
  border-left-width:4px;border-radius:10px;padding:12px 14px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.rm-head{display:flex;align-items:center;gap:8px;margin-bottom:4px}
.rm-stage{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.rm-title{font-size:16px;margin:0 0 8px}
.rm-money{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.rm-sum{font-size:20px;font-weight:700;font-variant-numeric:tabular-nums}
.rm-state{font-size:13px;color:var(--muted)}
.rm-note,.rm-text{font-size:13.5px;color:var(--muted);margin:6px 0 0}
.rm-text{color:var(--ink-soft)}
.rm-items{margin-top:8px;font-size:13.5px}
.rm-items summary{cursor:pointer;color:var(--muted)}
.rm-items ul{margin:6px 0 0;padding-left:18px}
.rm-items li{margin:3px 0}
.rm-items form{display:inline}
.rm-empty{margin:8px 0 0;font-size:13.5px}
.rm-tools{margin-top:8px;font-size:13px}

/* ⭐ Цвет несёт ДЕНЬГИ, а не состояние этапа: заказчик открывает роадмап с
 * вопросом «за что я заплатил и что ещё должен». Состояние этапа подписано
 * словом — оно важно, но вторым. */
.rm-block.is-paid{border-left-color:var(--green)}
.rm-block.is-paid_with_tail{border-left-color:var(--green)}
.rm-block.is-partial{border-left-color:var(--warn-ink)}
.rm-block.is-unpaid{border-left-color:var(--red)}
.rm-block.is-unbilled{border-left-color:var(--muted);border-left-style:dashed}
.rm-block.is-empty{border-left-color:var(--line)}
.rm-block.stage-done .rm-title{text-decoration:none}
.rm-block.stage-planned{opacity:.85}

/* Правка: блок таскается за карточку. Курсор обязан это обещать. */
.rm[data-save] .rm-block{cursor:grab;user-select:none}
.rm[data-save] .rm-block.dragging{cursor:grabbing;z-index:5;box-shadow:0 6px 18px rgba(0,0,0,.18)}
.rm[data-save] .rm-block details,.rm[data-save] .rm-block a,
.rm[data-save] .rm-block button{cursor:pointer;user-select:text}

/* ⛔ Полотно шире колонки текста. Колонка ограничена 960 px ради читаемости
 * прозы, но роадмап — не проза: в 960 px восемь этапов встают друг на друга и
 * половина уезжает за край. Выламываемся из колонки, оставаясь по центру.
 *
 * ⚠️ Ширина в `vw`, но НЕ 100vw: сто процентов ширины окна включают полосу
 * прокрутки, и страница получает горизонтальный сдвиг на её толщину — тот
 * самый «чуть-чуть едет вправо», который потом ищут часами. */
.rm-wrap{
  width: min(96vw, 1900px);
  margin-left: calc(50% - min(48vw, 950px));
  margin-right: calc(50% - min(48vw, 950px));
}
@media (max-width: 900px){
  /* На узком экране выламываться некуда — полотно живёт в колонке и
   * прокручивается вбок. */
  .rm-wrap{width:auto;margin-left:0;margin-right:0}
}

/* Лента событий заказчика (VISION §3.3). ⚠️ Цвет НЕСЁТ СМЫСЛ, поэтому он
   никогда не единственный носитель: рядом со точкой всегда стоит текст, а сама
   точка помечена aria-hidden — озвучивать «жёлтый кружок» нечего, смысл в
   словах строки. */
.feed{list-style:none;padding:0;margin:16px 0;display:flex;flex-direction:column;gap:2px}
.feed-row{display:flex;align-items:baseline;gap:10px;padding:8px 10px;border-radius:8px}
.feed-row:hover{background:var(--soft)}
.feed-when{flex:0 0 auto;font-variant-numeric:tabular-nums}
.feed-what{flex:1 1 auto;min-width:0}
.feed-dot{flex:0 0 auto;width:9px;height:9px;border-radius:50%;background:var(--muted);
          border:1px solid var(--line)}
.feed-red .feed-dot{background:var(--red);border-color:var(--red-line)}
.feed-yellow .feed-dot{background:var(--warn-ink);border-color:var(--warn-ink)}
.feed-green .feed-dot{background:var(--green);border-color:var(--green-line)}

/* Поле тегов в форме узла. ⭐ Галочка спрятана, а нажимают на саму метку: тег и
   так выглядит плашкой, и квадратик рядом с ней добавлял бы вторую мишень для
   одного действия. Состояние читается цветом рамки, а не значком. */
.tags-field{border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin:0 0 14px}
.tags-field legend{padding:0 6px;font-size:13px;color:var(--muted)}
.tags-field__chips{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 8px}
.tag-pick{cursor:pointer}
.tag-pick input{position:absolute;opacity:0;width:0;height:0}
.tag-pick .tag{border:1px solid transparent;opacity:.55}
.tag-pick input:checked + .tag{opacity:1;border-color:currentColor}
.tag-pick input:focus-visible + .tag{outline:2px solid var(--accent);outline-offset:2px}
/* ------------------------------------------------- модуль «Вики»/«Документация»
   ⛔ Цвета — только из палитры. Выдуманная переменная со светлым запасным
   значением уже ломала тёмную тему (16.08.2026): нет цвета в палитре — заводи
   его в палитре на обе темы, а не литералом по месту. */
.wiki-разделы { display:flex; gap:.4rem; flex-wrap:wrap; margin:.6rem 0 1rem; }
.wiki-полотно { display:grid; grid-template-columns:minmax(200px,280px) 1fr;
                gap:1.4rem; align-items:start; }
@media (max-width:720px) { .wiki-полотно { grid-template-columns:1fr; } }
.wiki-дерево ul { list-style:none; margin:0; padding-left:.9rem; }
.wiki-дерево > ul { padding-left:0; }
.wiki-дерево li { margin:.15rem 0; }
.wiki-дерево a { display:block; padding:.15rem .3rem; border-radius:4px;
                 color:var(--ink); text-decoration:none; }
.wiki-дерево a:hover { background:var(--soft); }
.wiki-дерево a.on { background:var(--blue-bg); color:var(--accent); }
.wiki-знак { display:inline-block; width:1em; color:var(--muted); }
.wiki-знак.wiki-open_self, .wiki-знак.wiki-open_inherited { color:var(--green); }
.wiki-знак.wiki-blocked { color:var(--red); }
.wiki-пропала { font-size:.8em; color:var(--red); margin-left:.3rem; }
.wiki-легенда { margin-top:1rem; font-size:.85em; }
/* Плашка «видно ли заказчику» — заметная по требованию дизайна: она
   единственная страховка живого зеркала. */
.wiki-плашка { display:flex; flex-direction:column; gap:.2rem; padding:.6rem .8rem;
               border-left:3px solid var(--line); background:var(--soft);
               border-radius:6px; margin-bottom:1rem; }
.wiki-плашка.wiki-open_self, .wiki-плашка.wiki-open_inherited {
    border-left-color:var(--green); background:var(--green-bg); }
.wiki-плашка.wiki-blocked { border-left-color:var(--red); background:var(--red-bg); }
.wiki-решение { display:flex; gap:.4rem; flex-wrap:wrap; margin:.6rem 0 1.2rem; }
.wiki-тропа { margin:.2rem 0 .6rem; }
.wiki-содержимое img { max-width:100%; height:auto; }
.wiki-содержимое table { border-collapse:collapse; width:100%; }
.wiki-содержимое th, .wiki-содержимое td { border:1px solid var(--line);
                                           padding:.3rem .5rem; text-align:left; }
/* ⛔ Погашенная ссылка и неопубликованная иллюстрация НАЗЫВАЮТСЯ приглушённым
   текстом, а не исчезают: пропавший кусок фразы читается как поломка. */
.wiki-снято { color:var(--muted); }

/* Таблица прокручивается сама: длинные значения не расширяют весь кабинет. */
.table-scroll{max-width:100%;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}
.page{width:100%;min-width:0}
.nav-toggle,.rail__canvas{display:none}

@media (max-width:720px){
  .topbar-inner,body.full .topbar-inner{padding:8px 12px}
  .topbar-inner{gap:8px}
  .topbar .brand{font-size:18px;flex:none}
  .topbar .who{flex:1;min-width:0;gap:8px;font-size:12px;white-space:nowrap;justify-content:flex-end}
  .topbar .who > span:first-child{min-width:0}
  .topbar .who > button,.topbar .who > a{flex:none}
  .topbar .nav-toggle{display:block;flex:none;padding:5px 9px;order:2}
  .topbar nav{order:3;flex:1 0 100%;flex-direction:column;gap:0}
  .topbar.nav-ready:not(.nav-open) nav{display:none}
  .topbar nav a{padding:10px 8px}
  .subnav{flex:none;min-width:0}
  .subnav-inner{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .subnav a{flex:none;white-space:nowrap}
  .rm-wrap{max-width:100%;max-height:70vh;overflow:auto}
  .graph-stage{max-height:70vh;overflow:auto}

  /* Работы и полотно делят один экран; календарь использует свою оболочку. */
  body.graph-page{height:100vh;height:100dvh;overflow:hidden}
  .graph-page main.shell,.graph-page.side-hidden main.shell{
    display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);
  }
  .graph-page .rail{grid-row:1;flex-direction:row;overflow-x:auto;min-width:0;
    border-right:0;border-bottom:1px solid var(--line)}
  .graph-page .rail__label{display:none}
  .graph-page .rail__canvas{display:block;flex:none}
  .graph-page .shell__side,.graph-page .shell__canvas{grid-row:2;grid-column:1;min-height:0}
  .graph-page .shell__scroll{height:100%;overflow:auto}
  .graph-page:not(.side-hidden) .shell__canvas{visibility:hidden;pointer-events:none}
  .graph-page.side-hidden .shell__side{display:none}
  .graph-page .shell__toggle,.graph-page .shell__resizer{display:none}
  .graph-page .graph-stage--full{height:100%;max-height:none;overflow:auto}
}
