/*
 * WellStudio Web — оформление SPA (Фаза D, пакет D2).
 *
 * Тёмная тема по умолчанию, светлая — по prefers-color-scheme.
 * Системные шрифты, никаких внешних ресурсов (CSP default-src 'self').
 */

:root {
  --bg: #14171c;
  --bg-elevated: #1b1f26;
  --bg-input: #10131a;
  --border: #2a3039;
  --border-strong: #3a4250;
  --text: #e6e9ef;
  --text-muted: #9aa4b2;
  --accent: #4c9aff;
  --accent-contrast: #0b1220;
  --danger: #ff6b6b;
  --row-hover: #232833;
  --radius: 8px;
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    "Noto Sans", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas,
    "Liberation Mono", monospace;
  color-scheme: dark light;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f4f6f9;
    --bg-elevated: #ffffff;
    --bg-input: #ffffff;
    --border: #d7dde5;
    --border-strong: #b9c2ce;
    --text: #1b2028;
    --text-muted: #5d6875;
    --accent: #1668dc;
    --accent-contrast: #ffffff;
    --danger: #c92a2a;
    --row-hover: #eef2f7;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  /* Мобильная раскладка (Фаза M, пакет M2): горизонтальный скролл СТРАНИЦЫ
     запрещён — прокручиваться вправо-влево имеет право только лента треков
     внутри вьюера, у неё свой контейнер с overflow. */
  max-width: 100%;
  overflow-x: hidden;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.45;
}

.app-root {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

h1 {
  font-size: 20px;
  font-weight: 600;
  margin: 0;
}

.muted {
  color: var(--text-muted);
}

.error {
  color: var(--danger);
}

.screen-message {
  padding: 16px;
  color: var(--text-muted);
}

/* ----------------------------------------------------------- кнопки/поля */

.btn {
  appearance: none;
  border: 1px solid var(--border-strong);
  background: var(--bg-elevated);
  color: var(--text);
  font: inherit;
  padding: 6px 14px;
  border-radius: var(--radius);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.btn:hover {
  border-color: var(--accent);
}

.btn:disabled {
  opacity: 0.6;
  cursor: default;
}

/* .btn сама задаёт display — UA-правило [hidden]{display:none} слабее
   author-стилей, и кнопка, скрытая из JS по роли (importBtn/sourcesBtn
   для reader'а, «+ Источник» на sources-странице), оставалась бы
   видимой. Глушим для всех кнопок разом. */
.btn[hidden] {
  display: none;
}

.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  justify-content: center;
}

.btn.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
  padding-left: 0;
}

.btn.ghost:hover {
  color: var(--accent);
}

/* --- «иконка + подпись» (M2) ---------------------------------------------
   Кнопка шапки собирается из двух span'ов: .btn-icon (глиф) и .btn-text
   (подпись). На узком экране подпись скрывается — остаётся зона-иконка
   ≥44px. (narrow-only-глифы убраны Фазой U вместе со старой шапкой; сама
   было (💾 у «Сохранить вид»): он появляется только вместо подписи. */
.btn-icon {
  display: inline-flex;
  line-height: 1;
  flex: none;
}


.btn-text {
  white-space: nowrap;
}

.icon-btn {
  justify-content: center;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--text-muted);
  font-size: 13px;
}

/* Тот же приём: .field задаёт display:flex, поэтому [hidden] нужно
   переопределять явно — иначе поле, скрытое из JS (wellboreField «Ствол
   проекта» в sources.js, kindField диалога загрузки), остаётся видимым. */
.field[hidden] {
  display: none;
}

.field input {
  font: inherit;
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 8px 10px;
}

.field input:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

/* -------------------------------------------------------------- #/login */

.login-screen {
  flex: 1;
  display: grid;
  place-items: center;
  padding: 24px;
}

.card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
}

.login-card {
  width: min(380px, 100%);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Логотип над заголовком входа (разметку завела Фаза Z, пакет Z3; правило
   — пакет П2, до него класс висел без единой строки стиля, а размер
   держали только атрибуты width/height).

   Почему рамка. Внутри icon.svg своя тёмная подложка (#14171c) со
   скруглением 18.75% — в тёмной теме карточка входа (#1b1f26) почти того
   же тона, и квадрат логотипа терял края. Рамка цветом var(--border)
   возвращает границу и в тёмной теме, и в светлой (там она отделяет
   тёмный квадрат от белой карточки мягче, чем резкий стык).

   align-self обязателен: .login-card — flex-column, по умолчанию потомки
   растягиваются по ширине карточки. */
.login-logo {
  align-self: flex-start;
  width: 40px;
  height: 40px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  border-radius: 19%;
}

.login-title {
  font-size: 18px;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 12px;
}

/* Поля входа: 16px — НИЖНЯЯ граница, при меньшем размере Safari на iOS
   зумит страницу при фокусе (и уже не отзумливает). 44px — тач-зона. */
.login-card .field input {
  font-size: 16px;
  min-height: 44px;
}

/* Кнопка «Войти» тянется на всю форму (flex-column + stretch), явная
   высота — та же тач-зона. */
.login-form .btn.primary {
  min-height: 44px;
}

/* Плашка сообщения под формой — три тона одной геометрии (пакет П2).
   Раньше класс был один, красный, и страница источников печатала им же
   «Проверяю подключение…» и «Подключение есть · сервер X»: успешная
   проверка подключения выглядела как отказ. Теперь тон выбирает JS
   (js/views/sources.js::setFormMessage), а вместе с классом меняется и
   ARIA-роль плашки: alert у ошибки, status у двух остальных.
   Ни один из трёх НЕ задаёт display — нативный `hidden` их прячет. */
.form-error,
.form-ok,
.form-info {
  margin: 10px 0 0;
  font-size: 13px;
}

.form-error {
  color: var(--danger);
}

/* Зелёный «получилось» — ОДИН литерал на обе темы, без переменной.
   Своей переменной темы для успеха в проекте нет, а завести её здесь
   нельзя: значения тем живут в theme.css (файл вне зоны этого пакета), и
   канарейка tests/test_server_webapp_theme.py прямо запрещает блоки
   `:root[data-theme=…] { … }` с переменными в app.css. Один литерал
   надёжнее пары «:root + @media»: при ЯВНО выбранной теме (js/theme.js
   ставит data-theme, медиазапрос при этом не срабатывает) пара дала бы
   светло-зелёный текст на белой карточке. #2b8a3e читается и на тёмном
   фоне карточки (#1b1f26), и на белом — тот же приём, что у .warn-mark
   выше по файлу, у которой оранжевый тоже задан литералом. */
.form-ok {
  color: #2b8a3e;
}

.form-info {
  color: var(--text-muted);
}

/* Кнопка «Повторить» в плашке ошибки загрузки (пакет П2): каталог
   проектов и страница источников до него оставляли единственным выходом
   F5. Отступ от текста — единственное, что нужно поверх .btn.small. */
.retry-btn {
  margin-top: 10px;
}

/* Действия пустого каталога — «Создать проект» / «Импорт .wsp» (пакет П2):
   до него пустое состояние советовало загрузить .wsp, но кнопки для этого
   на экране каталога не было вовсе. Отступы повторяют .screen-message
   (padding: 16px), чтобы кнопки встали ровно под её текстом. */
.empty-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 0 16px 16px;
}

/* --------------------------------------------------------- SSO (Фаза O) */

/* Разделитель между парольной формой и кнопкой SSO — линии по бокам,
   подпись "или" по центру. Показывается, только когда провайдер включён
   (js/views/login.js по ответу api.getOidcStatus()); .sso-divider САМА
   задаёт display, поэтому [hidden] нужно переопределять явно — тот же
   приём, что у .doc-banner[hidden] ниже по файлу. */
.sso-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
  color: var(--text-muted);
  font-size: 12px;
}

.sso-divider[hidden] {
  display: none;
}

.sso-divider::before,
.sso-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* Кнопка «Войти через <provider_name>» — альтернативный путь входа,
   поэтому не .primary (акцентная заливка остаётся у пароля). [hidden]
   переопределяется по той же причине, что и у .btn.danger[hidden] ниже:
   .btn задаёт display: inline-flex. */
.sso-btn {
  width: 100%;
  min-height: 44px;
  justify-content: center;
  margin-top: 10px;
}

.sso-btn[hidden] {
  display: none;
}

/* ----------------------------------------------------------- общая шапка */

.page {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.page-header {
  display: flex;
  align-items: center;
  /* Шапка живёт в ОДНУ строку на любой ширине (M2): переносить нечего —
     заголовок ужимается ellipsis, кнопки сжимаются до иконок. */
  flex-wrap: nowrap;
  gap: 12px;
  /* safe-area: вырез/скруглённые углы iOS не должны резать шапку.
     На десктопе env(...) = 0 — значения совпадают с прежними 12px/16px. */
  padding: calc(12px + env(safe-area-inset-top, 0px))
    calc(16px + env(safe-area-inset-right, 0px)) 12px
    calc(16px + env(safe-area-inset-left, 0px));
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
}

/* Заголовок страницы ужимается первым: min-width:0 обязателен, иначе flex
   не даст ему стать уже содержимого и шапка вылезет за экран. */
.page-header h1 {
  flex: 0 1 auto;
  min-width: 2ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page-tools {
  margin-left: auto;
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Имя пользователя в каталоге — вторично, режется первым */
.whoami {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page-body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 16px;
}

.rev-badge {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 10px;
}

/* Индикатор живого WebSocket-соединения (Фаза R): ● живо / ◌ переподключение
   / ○ офлайн. Значок ставит js/views/project.js, класс задаёт цвет. */
.live-badge {
  font-size: 13px;
  line-height: 1;
  cursor: default;
  color: var(--text-muted);
}

.live-badge.live {
  color: #2ecc71;
}

.live-badge.connecting {
  color: #f0a83c;
}

.live-badge.off {
  color: var(--text-muted);
  opacity: 0.7;
}

/* Плашка «документ обновлён другим пользователем» (событие doc, Фаза R) */
.doc-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
  font-size: 13px;
  color: var(--text-muted);
}

.doc-banner[hidden] {
  display: none;
}

.doc-banner-text {
  flex: 1;
  min-width: 0;
}

/* Полоса «нет сети» (Фаза AA, пакет AA3) — единственная на всё приложение.
   Ставит js/ui/messages.js::createOfflineBanner в <body> РЯДОМ с #app:
   роутер очищает содержимое outlet на каждом переходе, и полоса внутри
   него исчезала бы вместе с видом. Отсюда position: fixed — обычный поток
   двигал бы раскладку (у .app-root min-height: 100vh) и давал прокрутку
   на пустом месте. Скрытие — тот же приём, что у .doc-banner выше:
   display:flex сильнее UA-правила [hidden]{display:none}. */
.net-banner {
  position: fixed;
  z-index: 40;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 16px;
  border-bottom: 1px solid #f0a83c;
  background: var(--bg-elevated);
  color: var(--text);
  font-size: 13px;
}

.net-banner[hidden] {
  display: none;
}

.net-banner-text {
  flex: 1;
  min-width: 0;
}

.btn.small {
  padding: 2px 10px;
  font-size: 12px;
}

/* =========================================================================
   ЛЕНТА ДЕЙСТВИЙ (ribbon) — Фаза U, пакет U1
   =========================================================================

   Разметку строит js/ui/ribbon.js (там же ASCII-схема); здесь — ВСЁ её
   оформление: компонент не ставит ни одного инлайн-стиля (CSP
   `default-src 'self'` режет разметку стилей) и не подписывается на
   медиазапросы — узкий экран разбирает блок @media в конце этой секции.

   Строение: .rb-root ⊃ .rb-tabs (строка вкладок) + .rb-panels ⊃ .rb-panel
   (полоса групп активной вкладки) ⊃ .rb-group / .rb-sep. Внутри группы —
   .rb-group-body (крупные .rb-action.rb-large слева, мелкие
   .rb-action.rb-small столбиками .rb-smalls) и подпись .rb-group-label
   СНИЗУ, как в desktop-ленте.

   Скрытые элементы компонент гасит свойством `hidden`, поэтому у каждого
   правила с явным display есть пара `[hidden]{display:none}` — тот же
   приём, что у .doc-banner/.sso-btn выше по файлу. */

.rb-root {
  flex: none;
  display: flex;
  flex-direction: column;
  position: relative; /* якорь для .rb-peek — временной панели (Фаза S) */
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
}

/* Строка вкладок: на узком экране прокручивается горизонтально — вкладки
   не сжимаются (flex:none) и не переносятся. */
.rb-tabs {
  display: flex;
  align-items: stretch;
  gap: 2px;
  padding: 0 8px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}

.rb-tab {
  appearance: none;
  flex: none;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  padding: 6px 14px;
  cursor: pointer;
  white-space: nowrap;
}

.rb-tab[hidden] {
  display: none;
}

.rb-tab:hover {
  color: var(--text);
}

/* Активная вкладка: фон полосы групп + акцентная линия сверху. Классический
   трюк «уехать на -1px под общую границу» тут НЕ работает — .rb-tabs
   прокручивается (overflow-x), а скролл-контейнер обрезает вылезающее. */
.rb-tab.is-active {
  color: var(--text);
  background: var(--bg-elevated);
  border-color: var(--border);
  font-weight: 600;
  box-shadow: inset 0 2px 0 var(--accent);
}

.rb-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.rb-panels {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.rb-panel {
  display: flex;
  align-items: stretch;
  padding: 6px 8px 4px;
  overflow-x: auto;
  scrollbar-width: thin;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}

.rb-panel[hidden] {
  display: none;
}

.rb-group {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 0 10px;
}

.rb-group[hidden] {
  display: none;
}

.rb-group-body {
  flex: 1;
  display: flex;
  align-items: stretch;
  gap: 6px;
}

.rb-group-label {
  text-align: center;
  font-size: 11px;
  line-height: 1.2;
  color: var(--text-muted);
  white-space: nowrap;
}

/* Вертикальный разделитель между группами — отдельный элемент: скрытая
   группа не должна оставлять висящую линию (видимость считает JS). */
.rb-sep {
  flex: none;
  align-self: stretch;
  width: 1px;
  margin: 2px 0;
  background: var(--border);
}

.rb-sep[hidden] {
  display: none;
}

/* Столбик мелких кнопок (до SMALL_COLUMN_ROWS строк, см. ribbon.js) */
.rb-smalls {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
}

.rb-action {
  appearance: none;
  position: relative; /* якорь для .rb-badge */
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.rb-action[hidden] {
  display: none;
}

.rb-action:hover:not(:disabled) {
  background: var(--row-hover);
  border-color: var(--border);
}

.rb-action:disabled {
  opacity: 0.5;
  cursor: default;
}

.rb-action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.rb-action.rb-large {
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 76px;
  min-height: 64px;
  padding: 6px 4px;
  text-align: center;
}

.rb-action.rb-small {
  justify-content: flex-start;
  min-height: 20px;
  padding: 2px 8px 2px 4px;
  font-size: 12px;
}

.rb-large .rb-label {
  line-height: 1.15;
  overflow-wrap: anywhere; /* «Сохранить вид» переносится в две строки */
}

.rb-small .rb-label {
  white-space: nowrap;
}

.rb-icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.rb-large .rb-icon {
  width: 32px;
  height: 32px;
}

.rb-small .rb-icon {
  width: 18px;
  height: 18px;
}

.rb-icon-img {
  max-width: 100%;
  max-height: 100%;
}

/* Фолбэк, когда иконки нет (opts.iconUrl вернул null): буква подписи */
.rb-icon-text {
  border-radius: var(--radius);
  background: var(--row-hover);
  color: var(--text-muted);
  font-weight: 600;
}

.rb-large .rb-icon-text {
  font-size: 15px;
}

.rb-small .rb-icon-text {
  font-size: 11px;
}

.rb-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 10px;
  line-height: 16px;
  text-align: center;
}

.rb-badge[hidden] {
  display: none;
}

/* --- сворачивание ленты (Фаза S, пакет S2) -------------------------------

   Включает его ключ opts.collapseKey (js/ui/ribbon.js): компонент ставит
   на корень `rb-collapsed` (полоса групп убрана) и `rb-peek` (временный
   показ панели поверх контента, до первого действия/Esc/клика мимо). Вся
   геометрия показа — здесь: в JS по-прежнему ни одного el.style, поэтому
   «поверх» описывается только CSS'ом. */

.rb-collapse-btn {
  appearance: none;
  flex: none;
  align-self: center;
  margin-left: auto; /* прижать к правому краю строки вкладок */
  position: sticky; /* на узкой прокручиваемой строке шеврон не уезжает */
  right: 0;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 2px 8px;
  background: var(--bg);
  color: var(--text-muted);
  font: inherit;
  line-height: 1.2;
  cursor: pointer;
}

.rb-collapse-btn[hidden] {
  display: none;
}

.rb-collapse-btn:hover {
  color: var(--text);
  border-color: var(--border);
}

.rb-collapse-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Свёрнуто: от ленты остаётся строка вкладок, полосы групп нет вовсе */
.rb-collapsed .rb-panels {
  display: none;
}

/* …кроме временного показа: панель выпадает ПОД строкой вкладок поверх
   контента, не раздвигая его. z-index 25 — выше мобильной панели дерева
   (20) и её подложки (15), ниже попапа комментария (30), модалки (40) и
   тоста (50): подглядывание за лентой не должно перекрывать диалог. */
.rb-collapsed.rb-peek .rb-panels {
  display: flex;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 25;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border-strong);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.45);
}

@media (prefers-color-scheme: light) {
  /* Блик у ленты тёмной темы светлый, у светлой — наоборот тень */
  .rb-panel {
    box-shadow: inset 0 1px 0 rgba(27, 32, 40, 0.04);
  }

  /* Тень выпадающей панели в светлой теме мягче — чёрная выглядит грязью */
  .rb-collapsed.rb-peek .rb-panels {
    box-shadow: 0 10px 24px rgba(27, 32, 40, 0.18);
  }
}

/* Узкий экран (тот же порог 820px, что у остальной мобильной раскладки):
   подписи действий уходят, остаются иконки с тач-зоной 44px, мелкие
   кнопки встают в ряд (столбик из трёх по 44px был бы выше экрана).
   Подписи ГРУПП остаются — без подписей действий это единственный текст,
   по которому лента читается. */
@media (max-width: 820px) {
  .rb-tabs {
    padding: 0 4px;
  }

  .rb-tab {
    padding: 8px 12px;
    min-height: 40px;
  }

  .rb-root .rb-label {
    display: none;
  }

  .rb-panel {
    padding: 4px 4px 2px;
  }

  .rb-group {
    padding: 0 6px;
  }

  .rb-smalls {
    flex-direction: row;
    align-items: center;
  }

  .rb-action.rb-large,
  .rb-action.rb-small {
    width: 44px;
    min-width: 44px;
    min-height: 44px;
    justify-content: center;
    padding: 4px;
  }

  .rb-root .rb-small .rb-icon {
    width: 22px;
    height: 22px;
  }

  /* Шеврон сворачивания — такая же тач-зона 44px, как у действий */
  .rb-collapse-btn {
    min-width: 44px;
    min-height: 44px;
    padding: 4px 10px;
  }
}

/* ----------------------------------------------------------- #/projects */

.table-wrap {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-elevated);
}

.grid-table {
  width: 100%;
  border-collapse: collapse;
}

.grid-table th,
.grid-table td {
  text-align: left;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.grid-table th {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  font-weight: 600;
}

.grid-table tbody tr:last-child td {
  border-bottom: none;
}

.grid-table td.name {
  white-space: normal;
  font-weight: 500;
}

.grid-table .num {
  text-align: right;
  font-family: var(--mono);
}

.row-link {
  cursor: pointer;
}

.row-link:hover td,
.row-link:focus-visible td {
  background: var(--row-hover);
}

.row-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* --- карточки каталога (M2) ----------------------------------------------
   На узком экране таблица нечитаема (5 колонок в 360px), поэтому
   js/views/projects.js рисует вместо неё карточки: имя крупно, вся
   карточка — одна кнопка. Стили общие (класс ставит JS только на узком
   экране), тач-состояние — :active, клавиатурное — :focus-visible. */
.card-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}

.project-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  width: 100%;
  min-height: 64px;
  padding: 12px 14px;
  text-align: left;
  font: inherit;
  color: var(--text);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.project-card-name {
  font-size: 17px;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.project-card-meta {
  font-size: 13px;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.project-card:hover {
  border-color: var(--border-strong);
}

.project-card:active,
.project-card.active {
  background: var(--row-hover);
  border-color: var(--accent);
}

.project-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ---------------------------------------------------------- #/p/{pid} */

.project-layout {
  flex: 1;
  min-height: 0;
  display: grid;
  /* Ширина панели дерева — переменная (не литерал в шаблоне): её двигает
     .tree-resize-handle (см. ниже), значение приходит из localStorage
     ("ws.tree.width", js/views/project.js) или дефолта 260px. Средняя
     колонка — сама рукоятка (6px, свой grid-track, а не border на
     .tree-pane — так курсор col-resize не наезжает силой ::before). */
  grid-template-columns: var(--tree-width, 260px) 6px 1fr;
}

.tree-pane {
  background: var(--bg-elevated);
  overflow: auto;
  padding: 10px 8px 24px;
  min-height: 0;
}

/* Рукоятка изменения ширины дерева (широкий экран, .project-layout —
   3-колоночный грид). Полоса тоньше зоны захвата: ::before расширяет
   кликабельную область на ±3px без изменения раскладки соседей. */
.tree-resize-handle {
  position: relative;
  grid-row: 1;
  cursor: col-resize;
  background: var(--border);
  touch-action: none;
}

.tree-resize-handle::before {
  content: "";
  position: absolute;
  inset: 0 -3px;
}

.tree-resize-handle:hover,
.tree-resize-handle.is-dragging {
  background: var(--accent);
}

/* Пока рукоятка тянется — курсор и запрет выделения текста на ВСЮ
   страницу (иначе быстрое движение мыши цепляет соседний текст/iframe
   вьюера теряет указатель между кадрами). Класс ставит и снимает JS. */
body.tree-resizing {
  cursor: col-resize;
  user-select: none;
}

/* --- выдвижная панель дерева (M2) ----------------------------------------
   На широком экране дерево — колонка грида, и три элемента ниже не нужны:
   кнопка «☰» в шапке, шапка панели с крестиком и подложка-затемнение.
   Они включаются ТОЛЬКО в @media (max-width: 820px) ниже; состояние
   («открыта») держит js/views/project.js классом .nav-open на .page. */
.nav-toggle,
.tree-pane-head,
.tree-backdrop {
  display: none;
}

.tree-pane-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

.tree-status {
  margin: 0 4px 8px;
  font-size: 12px;
  color: var(--text-muted);
}

.tree-status.error {
  color: var(--danger);
}

.tree-node > summary {
  cursor: pointer;
  padding: 3px 4px;
  border-radius: 6px;
  display: flex;
  align-items: baseline;
  gap: 6px;
  /* display:flex убирает нативный треугольник — рисуем свой (макет §4) */
  list-style: none;
}

.tree-node > summary::-webkit-details-marker {
  display: none;
}

.tree-node > summary::before {
  content: "▸";
  color: var(--text-muted);
  font-size: 11px;
  width: 10px;
  flex: none;
}

.tree-node[open] > summary::before {
  content: "▾";
}

.tree-node > summary:hover {
  background: var(--row-hover);
}

.tree-children {
  padding-left: 14px;
  border-left: 1px solid var(--border);
  margin-left: 6px;
}

.node-name {
  font-weight: 500;
}

.node-meta {
  font-size: 11px;
  color: var(--text-muted);
  font-family: var(--mono);
}

.tree-note {
  margin: 2px 0 2px 6px;
  font-size: 12px;
  color: var(--text-muted);
}

.tree-leaf.curve {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  text-align: left;
  font: inherit;
  color: var(--text);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 2px 6px;
  cursor: pointer;
}

.tree-leaf.curve::before {
  content: "•";
  color: var(--text-muted);
}

.tree-leaf.curve:hover {
  background: var(--row-hover);
}

.tree-leaf.curve.active {
  border-color: var(--accent);
  color: var(--accent);
}

.tree-leaf.curve.disabled {
  color: var(--text-muted);
  cursor: default;
  opacity: 0.65;
}

.curve-unit {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-muted);
  margin-left: auto;
}

/* ------------------------------------------------- геология (Фаза L, L3) */

/* Узлы «Литология»/«Шламограмма»/«Стратиграфия»/«Насыщение» — тот же лист,
   что у кривых/имиджей (.tree-leaf.curve даёт раскладку/hover/active), маркер
   отличается: интервальная колонка, а не отдельная точка данных. */
.tree-leaf.curve.geology::before {
  content: "▬";
  color: var(--text-muted);
}

.viewer-pane {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.viewer-host {
  flex: 1;
  min-height: 0;
  position: relative;
  overflow: hidden;
}

/* ------------------------------------------ редактирование (Фаза E, E2) */

/* Инструменты шапки: «💬 Комментарий», «Сохранить вид», статус операции.
   Отступ меньше общего .page-tools — кнопки читаются как одна группа. */
.project-page .page-tools {
  gap: 8px;
}

/* Короткий отчёт об операции («Вид сохранён» / текст ошибки) */
.save-status {
  font-size: 12px;
  color: var(--text-muted);
  max-width: 40ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.save-status.error {
  color: var(--danger);
}

.save-status[hidden] {
  display: none;
}

/* Точка «есть несохранённые правки» — внутри кнопки «Сохранить вид» */



/* Кнопка-переключатель режима комментария (активный режим — залит) */

.btn.danger {
  border-color: var(--danger);
  color: var(--danger);
}

.btn.danger:hover {
  border-color: var(--danger);
  background: var(--danger);
  color: var(--accent-contrast);
}

.btn.danger[hidden] {
  display: none;
}

/* Плашка конфликта сохранения (412) — та же полоса, что doc-banner R2,
   но с акцентной рамкой: это требует решения, а не просто уведомляет. */
.doc-banner.conflict {
  border-bottom-color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent);
  color: var(--text);
}

/* Попап комментария: карточка страницы, координаты ставит JS (CSSOM) */
.cmt-popup {
  position: fixed;
  z-index: 30;
  width: 280px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  color: var(--text);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.cmt-popup[hidden] {
  display: none;
}

.cmt-popup-title {
  font-size: 12px;
  font-family: var(--mono);
  color: var(--text-muted);
}

.cmt-popup-text {
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 6px 8px;
  resize: vertical;
  min-height: 60px;
}

.cmt-popup-text:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.cmt-popup-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.cmt-popup-row .btn.ghost {
  margin-left: auto;
  padding-left: 8px;
}

@media (prefers-color-scheme: light) {
  .cmt-popup {
    box-shadow: 0 8px 24px rgba(27, 32, 40, 0.18);
  }
}

/* ================================================== #/p/{pid}/sources (G3)
   Страница WITSML-источников: слева список (таблица > 820px, карточки уже —
   ветку выбирает js/views/sources.js), справа форма add/edit с каскадом
   «Обзор…». Классы .sources-* живут только здесь. */

.sources-body {
  padding-top: 12px;
}

/* Пока форма закрыта, список занимает всю ширину; класс `with-form` ставит
   JS (не `:has()` — правило обязано работать и в старых движках). */
.sources-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 16px;
}

.sources-layout.with-form {
  grid-template-columns: minmax(0, 1fr) minmax(0, 380px);
}

.sources-list {
  min-width: 0;
}

/* Полоса статуса операции под шапкой (прогон, ошибки, «кривые появятся…») */
.sources-status {
  padding: 6px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
  font-size: 13px;
  color: var(--text-muted);
}

.sources-status.error {
  color: var(--danger);
}

.sources-status[hidden] {
  display: none;
}

/* --- список источников --------------------------------------------------- */

.sources-table td {
  vertical-align: top;
}

.sources-table td.dot {
  width: 1%;
  padding-right: 0;
}

.sources-table td.key {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-muted);
}

.sources-table td.err {
  max-width: 22ch;
  white-space: normal;
}

.status-dot {
  font-size: 12px;
  line-height: 1;
  cursor: default;
}

.status-dot.ok {
  color: #2ecc71;
}

.status-dot.err {
  color: var(--danger);
}

.status-dot.off,
.status-dot.idle {
  color: var(--text-muted);
  opacity: 0.7;
}

.source-name {
  display: block;
  font-weight: 500;
}

.source-endpoint {
  display: block;
  max-width: 32ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--text-muted);
}

.source-error {
  color: var(--danger);
  font-size: 12px;
}

/* «прогон…» / «удаление…» — пометка строки, по которой идёт операция
   (пакет П2). До него глобальный флаг `busy` просто глотал повторный
   клик: кнопки оставались активными и ничем не показывали, что запрос
   уже в пути. Теперь кнопки строки уходят в disabled, а строка получает
   эту подпись. `display: block` — в ячейке таблицы она встаёт своей
   строкой под адресом (как .source-endpoint), в карточке — inline-flex
   шапки её и так переносит. */
.source-busy {
  display: block;
  font-size: 12px;
  color: var(--accent);
}

/* «⚠» — password_env не задан в окружении сервера (is_password_set: false) */
.warn-mark {
  margin-left: 6px;
  color: #f0a83c;
  cursor: default;
}

.source-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.run-btn {
  font-size: 11px;
  line-height: 1;
  padding: 4px 8px;
}

/* Переключатель enabled: галочка + подпись «вкл/выкл» */
.switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

.switch input {
  accent-color: var(--accent);
  width: 16px;
  height: 16px;
}

.switch-text {
  font-size: 12px;
  color: var(--text-muted);
}

.poll-hint {
  display: block;
  font-size: 11px;
  color: var(--text-muted);
}

/* --- карточки источников (узкий экран) ----------------------------------- */

.source-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.source-card-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.source-card-meta,
.source-card-error {
  font-size: 13px;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.source-card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}

/* --- форма add/edit ------------------------------------------------------ */

.sources-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  position: sticky;
  top: 0;
}

.sources-form[hidden] {
  display: none;
}

.form-title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.form-note {
  margin: 0;
  font-size: 12px;
  color: var(--text-muted);
}

.form-note[hidden] {
  display: none;
}

.form-grid {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.field-label {
  font-size: 12px;
}

.field select,
.field textarea {
  font: inherit;
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 8px 10px;
}

.field select:focus,
.field textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.field-hint {
  font-size: 11px;
  color: var(--text-muted);
}

.field-error {
  font-size: 12px;
  color: var(--danger);
}

.field-error[hidden] {
  display: none;
}

.form-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.form-row .btn.ghost {
  margin-left: auto;
}

/* --- каскад «Обзор…» ----------------------------------------------------- */

.discover-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-top: 1px solid var(--border);
  padding-top: 10px;
}

.discover-panel[hidden] {
  display: none;
}

.discover-status {
  margin: 0;
  font-size: 12px;
  color: var(--text-muted);
}

.discover-cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}

.discover-col {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 190px;
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px;
  background: var(--bg-input);
}

.discover-col-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.discover-note {
  margin: 0;
  font-size: 12px;
  color: var(--text-muted);
}

.discover-item {
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  text-align: left;
  padding: 4px 6px;
  border-radius: 6px;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.discover-item:hover {
  background: var(--row-hover);
}

.discover-item.active {
  border-color: var(--accent);
  background: var(--row-hover);
}

.mnemonic-box[hidden] {
  display: none;
}

.mnemonic-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.mnemonic-title {
  flex: 1;
  font-size: 12px;
  color: var(--text-muted);
}

.mnemonic-list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  max-height: 150px;
  overflow: auto;
  margin-top: 6px;
}

.mnemonic-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  cursor: pointer;
}

.mnemonic-item input {
  accent-color: var(--accent);
}

/* ============================================ загрузка данных (Фаза W, W2)
   Модальный диалог «⬆ Данные» на странице проекта: подложка на весь экран +
   карточка по центру. Классы .modal-* сделаны общими (карточка, шапка,
   тело, строка кнопок) — следующий диалог переиспользует их без копипаста;
   .import-* — только то, что специфично для загрузки файла.

   Всё, что задаёт себе display, обязано глушить [hidden] явно (UA-правило
   display:none слабее) — тот же приём, что у .doc-banner[hidden] выше. */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(6, 8, 12, 0.55);
}

.modal-backdrop[hidden] {
  display: none;
}

.modal-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(560px, 100%);
  max-height: min(88vh, 100%);
  padding: 16px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.45);
}

.modal-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.modal-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* .btn.modal-close, а не .modal-close: у .btn.ghost выше по файлу
   padding-left:0 с той же специфичностью — одиночный класс проиграл бы. */
.btn.modal-close {
  font-size: 20px;
  line-height: 1;
  padding: 0 6px;
}

/* Тело прокручивается само: карточка ограничена по высоте, а список кривых
   в LAS бывает на сотню строк. */
.modal-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-height: 0;
  overflow: auto;
}

.modal-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.modal-row .btn.ghost {
  margin-left: auto;
}

.import-card .field input[type="file"] {
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--bg-input);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  padding: 10px;
  cursor: pointer;
}

.import-status {
  margin: 0;
  font-size: 13px;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.import-status.error {
  color: var(--danger);
}

.import-status[hidden] {
  display: none;
}

.import-summary {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.import-summary[hidden] {
  display: none;
}

/* Факты разбора («Формат · Строк · Диапазон») — плитками в одну ленту */
.summary-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
}

/* Пара к display:flex (конвенция файла, см. .btn[hidden] вверху): без неё
   атрибут hidden у контейнера фактов НЕ прячет его — авторский flex сильнее
   UA-правила. Найдено живой проверкой фазы AP: диалоги прятали блоки
   атрибутом, а классы без пары оставляли их на экране. */
.summary-facts[hidden] {
  display: none;
}

.summary-fact {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.fact-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.fact-value {
  font-family: var(--mono);
  font-size: 13px;
  overflow-wrap: anywhere;
}

/* Скважина и ствол — рядом, пока хватает ширины */
.import-targets {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.import-curves {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Пара [hidden] к display:flex — см. комментарий у .summary-facts[hidden]. */
.import-curves[hidden] {
  display: none;
}

.curves-title {
  font-size: 12px;
  color: var(--text-muted);
}

.curves-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 180px;
  overflow: auto;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-input);
}

/* Строка кривой: мнемоника — роль — единица (единицу к правому краю
   прижимает общий .curve-unit из дерева проекта). */
.curve-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
}

.curve-role {
  font-size: 11px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Спиннер на кнопке: у fetch нет прогресса отправки, показывать нечего —
   кружок рядом с подписью честнее фальшивых процентов. */
.btn.busy::after {
  content: "";
  width: 12px;
  height: 12px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: ws-spin 0.8s linear infinite;
}

@keyframes ws-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Передача .wsp (js/ui/transfer_progress.js): текст + полоса + «Отмена».
   Полный вид — полоса каталога и полоса под шапкой узкого экрана: текст и
   кнопка первой строкой, полоса во всю ширину второй. `.compact` — строка
   шапки страницы проекта (там на статус ~40ch): всё в одну строку, полный
   текст — в подсказке. Ширину заливки ставит JS через CSSOM
   (`el.style.width`): инлайн-атрибут style режет CSP. */
.xfer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  padding: 10px 16px;
  font-size: 13px;
  color: var(--text-muted);
}

.xfer[hidden] {
  display: none;
}

.xfer-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}

.xfer-bar {
  position: relative;
  order: 3;
  flex: 1 0 100%;
  height: 6px;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--bg-input);
}

.xfer-fill {
  display: block;
  width: 0;
  height: 100%;
  background: var(--accent);
  transition: width 0.2s linear;
}

/* Длина неизвестна (обработка на сервере, сборка экспорта) — бегущий блик
   вместо фальшивых процентов. */
.xfer-bar.indeterminate .xfer-fill {
  width: 30%;
  transition: none;
  animation: xfer-slide 1.2s ease-in-out infinite;
}

@keyframes xfer-slide {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(340%);
  }
}

.xfer-cancel {
  flex: 0 0 auto;
  padding: 2px 10px;
}

.xfer-cancel[hidden] {
  display: none;
}

.xfer.compact {
  flex-wrap: nowrap;
  gap: 6px;
  max-width: 48ch;
  padding: 0;
  font-size: 12px;
}

.xfer.compact .xfer-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.xfer.compact .xfer-bar {
  order: 0;
  flex: 0 0 80px;
}

.xfer.compact .xfer-cancel {
  padding: 0 6px;
}

/* Узкий экран: виджет переезжает из шапки полосой под неё — тот же приём
   и та же подложка, что у `.project-page > .save-status` (Фаза M). */
.project-page > .xfer {
  padding: 6px calc(10px + env(safe-area-inset-right, 0px)) 6px
    calc(10px + env(safe-area-inset-left, 0px));
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
}

@media (prefers-reduced-motion: reduce) {
  .xfer-bar.indeterminate .xfer-fill {
    width: 100%;
    opacity: 0.45;
    animation: none;
  }
}

/* Тост об успешной загрузке — поверх всего, ~6 с (TOAST_MS в project.js) */
.page-toast {
  position: fixed;
  left: 50%;
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 50;
  max-width: min(520px, 92vw);
  padding: 10px 18px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--bg-elevated);
  color: var(--text);
  font-size: 13px;
  text-align: center;
  overflow-wrap: anywhere;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.page-toast[hidden] {
  display: none;
}

@media (prefers-color-scheme: light) {
  .modal-card {
    box-shadow: 0 16px 48px rgba(27, 32, 40, 0.22);
  }

  .modal-backdrop {
    background: rgba(27, 32, 40, 0.35);
  }

  .page-toast {
    box-shadow: 0 8px 24px rgba(27, 32, 40, 0.18);
  }
}

/* ============================================== гео-импорт и dnd (Фаза P,
   пакет P3): drag&drop файла на страницу проекта, дедуп-плашка и
   гео-предпросмотр диалога «⬆ Данные» — продолжение раздела W2 выше. */

/* Рамка-подсветка контейнера, пока пользователь тащит файл над страницей
   (js/views/project.js — onRootDragOver/onRootDragLeave/onRootDrop). Гейт
   editor+ там же (та же видимость, что у importBtn) — reader'у подсветка
   не покажется. */
.project-page.drag-over {
  outline: 2px dashed var(--accent);
  outline-offset: -4px;
}

/* Дедуп-плашка: «Возможный дубликат: name — detail» (может быть
   несколько строк) над кнопкой «Импортировать» — НЕ блокирует загрузку,
   только предупреждает (summary.duplicates, контракт P1). */
.import-duplicates {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 10px;
  border: 1px solid #f0a83c;
  border-radius: var(--radius);
  background: rgba(240, 168, 60, 0.14);
  color: var(--text);
  font-size: 12px;
}

.import-duplicates[hidden] {
  display: none;
}

.import-duplicate-row {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Гео-предпросмотр (mlplotter-ветка контракта P1: .lith/.shlam/.strat/
   .xcmt/.xlba): список строк summary.preview вместо блока кривых —
   curvesBox скрывается целиком, эта секция занимает её место. Список сам
   по себе стилизован общим .curves-list (см. раздел W2 выше). */
.import-geo-preview {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.import-geo-preview[hidden] {
  display: none;
}

.geo-preview-row {
  margin: 0;
  font-size: 12px;
  overflow-wrap: anywhere;
}

/* ========================================= #/p/{pid}/xs (Фаза X, пакет X2)
   Вход в кросс-секцию: read-only корреляционный вид нескольких скважин.
   Ниже — ТОЛЬКО каркасные классы страницы (шапка/лента/колонки/плашка);
   сам рендер (кривые, корреляционные линии, мини-литология) кладёт X1 в
   js/views/xsection.js через el.style.prop (CSSOM), как logview. */

.xs-page {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.xs-head {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 12px;
  padding: calc(12px + env(safe-area-inset-top, 0px))
    calc(16px + env(safe-area-inset-right, 0px)) 12px
    calc(16px + env(safe-area-inset-left, 0px));
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
}

/* Плашка предупреждения (напр. «TVD-оси в web пока не поддержаны — глубины
   по MD», план §«Ограничения MVP») — тот же амбер #f0a83c, что у
   .import-duplicates (Фаза P) и .warn-mark (Фаза G). */
.xs-warn {
  padding: 6px 16px;
  border-bottom: 1px solid var(--border);
  background: rgba(240, 168, 60, 0.14);
  color: var(--text);
  font-size: 13px;
}

.xs-warn[hidden] {
  display: none;
}

/* Горизонтальная лента колонок — скроллится САМА (тач и колесо), страница
   вбок не едет (тот же принцип, что у html/body overflow-x:hidden вверху
   файла, план §«Вертикальный viewport общий... Горизонтально — обычный
   скролл контейнера»). */
.xs-lane {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: stretch;
  overflow-x: auto;
  overflow-y: hidden;
  background: var(--bg);
}

/* Общая ось глубин слева в ленте — отдельная узкая полоса, не колонка */
.xs-axis {
  flex: none;
  width: 56px;
  border-right: 1px solid var(--border);
  background: var(--bg-elevated);
}

/* Колонка скважины: ширина из WellColumn.width_px, clamp 120..420px —
   границы задаёт X1 инлайн-стилем (CSSOM), здесь только рамки/раскладка. */
.xs-col {
  flex: none;
  min-width: 120px;
  max-width: 420px;
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.xs-col-head {
  flex: none;
  padding: 6px 8px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
  font-size: 12px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (prefers-color-scheme: light) {
  .xs-warn {
    background: rgba(240, 168, 60, 0.18);
  }
}

/* =========================================================================
   МОБИЛЬНАЯ РАСКЛАДКА (Фаза M, пакет M2) — единый брейкпоинт 820px
   =========================================================================

   Заменяет прежний узкоэкранный блок D2/E2 `@media (max-width: 720px)`:
   там дерево уезжало в верхнюю строку грида (30vh), шапке разрешался
   перенос (`flex-wrap: wrap`), а `.save-status` растягивался на всю
   ширину строки. Всё это переработано:
     • дерево            — выдвижная панель поверх вьюера (☰ / × / подложка);
     • шапка             — строго одна строка, кнопки сжаты до иконок ≥44px;
     • статус операции   — отдельная полоса под шапкой (переносит JS), иначе
                           он съедал ширину заголовка и выталкивал 💾 за экран.

   Порог 820px — планшет-портрет (768) и всё, что уже; тот же литерал
   продублирован в js/views/project.js и js/views/projects.js (matchMedia),
   при изменении править в трёх местах. */
@media (max-width: 820px) {
  /* --- шапка: одна строка, иконки, тач-зоны ---------------------------- */
  .page-header {
    gap: 6px;
    padding: calc(8px + env(safe-area-inset-top, 0px))
      calc(10px + env(safe-area-inset-right, 0px)) 8px
      calc(10px + env(safe-area-inset-left, 0px));
  }

  .page-header h1 {
    font-size: 16px;
  }

  .page-tools {
    gap: 6px;
  }

  /* Подписи прячем, глифы показываем — «Сохранить вид» → 💾 (+ точка
     несохранённых правок), «💬 Комментарий» → 💬, «← Проекты» → ←. */
  .btn-text {
    display: none;
  }

  
  .page-header .btn {
    min-height: 44px;
  }

  .icon-btn {
    min-width: 44px;
    padding-left: 8px;
    padding-right: 8px;
    font-size: 16px;
  }

  .btn.ghost.icon-btn {
    padding-left: 8px;
  }

  /* Статус операции не имеет права распирать шапку (иначе заголовок
     ужимается до пары пикселей, а кнопки уезжают за экран — замерено на
     375px). На узком экране js/views/project.js переносит его из
     .page-tools в саму страницу — он становится полосой под шапкой и
     читается целиком; в шапке остаются только кнопки. */
  .project-page > .save-status {
    max-width: none;
    padding: 5px calc(10px + env(safe-area-inset-right, 0px)) 5px
      calc(10px + env(safe-area-inset-left, 0px));
    background: var(--bg-elevated);
    border-bottom: 1px solid var(--border);
  }

  /* «rev 14» рядом с заголовком — компактнее, место нужнее имени проекта */
  .rev-badge {
    padding: 1px 7px;
    font-size: 11px;
  }

  /* --- страница проекта: дерево уходит в выдвижную панель --------------- */
  .project-layout {
    grid-template-columns: 1fr;
    /* контейнер позиционирования для панели и подложки: их бокс = ровно
       область под шапкой (и под плашками) до низа экрана */
    position: relative;
  }

  .nav-toggle {
    display: inline-flex;
  }

  /* Оверлей узкого экрана — своя фиксированная ширина (min(85vw,360px)
     ниже), рукоятке широкого грида здесь делать нечего. */
  .tree-resize-handle {
    display: none;
  }

  .tree-pane {
    position: absolute;
    z-index: 20;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(85vw, 360px);
    max-width: 100%;
    border-right: 1px solid var(--border-strong);
    box-shadow: 6px 0 24px rgba(0, 0, 0, 0.45);
    padding-left: calc(8px + env(safe-area-inset-left, 0px));
    padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-101%);
    visibility: hidden;
    transition: transform 0.18s ease, visibility 0.18s ease;
  }

  .project-page.nav-open .tree-pane {
    transform: none;
    visibility: visible;
  }

  .tree-pane-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 2px 8px 4px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 8px;
  }

  .tree-pane-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .tree-close {
    font-size: 20px;
    min-height: 40px;
    min-width: 40px;
  }

  .tree-backdrop {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 15;
    background: rgba(6, 8, 12, 0.5);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease;
  }

  .project-page.nav-open .tree-backdrop {
    opacity: 1;
    pointer-events: auto;
  }

  /* Пальцем в дерево: узлы и кривые получают нормальную тач-зону */
  .tree-node > summary,
  .tree-leaf.curve {
    min-height: 36px;
    align-items: center;
    padding-top: 6px;
    padding-bottom: 6px;
  }

  /* --- каталог, плашки, попап ------------------------------------------ */
  .page-body {
    padding: 12px calc(12px + env(safe-area-inset-right, 0px))
      calc(16px + env(safe-area-inset-bottom, 0px))
      calc(12px + env(safe-area-inset-left, 0px));
  }

  .whoami {
    max-width: 12ch;
  }

  /* Плашки «документ обновлён» / конфликта: кнопок до трёх — переносим */
  .doc-banner {
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px calc(10px + env(safe-area-inset-right, 0px)) 8px
      calc(10px + env(safe-area-inset-left, 0px));
  }

  .doc-banner-text {
    flex: 1 0 100%;
  }

  .doc-banner .btn {
    min-height: 36px;
  }

  /* --- источники (G3): форма под списком, каскад в одну колонку --------- */
  .sources-layout.with-form {
    grid-template-columns: minmax(0, 1fr);
  }

  .sources-form {
    position: static;
  }

  .discover-cols {
    grid-template-columns: minmax(0, 1fr);
  }

  .discover-col {
    max-height: 140px;
  }

  /* Тач-зоны: кнопки строки источника и пункты каскада пальцем */
  .source-actions .btn,
  .form-row .btn {
    min-height: 36px;
  }

  .discover-item,
  .mnemonic-item {
    min-height: 32px;
  }

  .sources-form .field input,
  .sources-form .field select {
    font-size: 16px; /* Safari iOS иначе зумит страницу при фокусе */
    min-height: 40px;
  }

  .cmt-popup {
    width: min(280px, 94vw);
    max-width: 94vw;
  }

  .cmt-popup-row {
    flex-wrap: wrap;
  }

  .cmt-popup-row .btn {
    min-height: 36px;
  }

  /* --- диалог загрузки данных (W2): на телефоне это лист во весь экран ---
     Карточка по центру с полями и списком кривых в 375px не помещается:
     подложка теряет отступы, карточка растягивается на весь экран без
     скруглений, а скважина/ствол встают друг под друга. */
  .modal-backdrop {
    padding: 0;
    align-items: stretch;
    justify-content: stretch;
  }

  .modal-card {
    width: 100%;
    max-height: 100%;
    border-radius: 0;
    border-left: none;
    border-right: none;
    padding: calc(10px + env(safe-area-inset-top, 0px))
      calc(12px + env(safe-area-inset-right, 0px))
      calc(12px + env(safe-area-inset-bottom, 0px))
      calc(12px + env(safe-area-inset-left, 0px));
  }

  .btn.modal-close {
    min-height: 40px;
    min-width: 40px;
  }

  .import-targets {
    grid-template-columns: minmax(0, 1fr);
  }

  .import-card .field input,
  .import-card .field select {
    font-size: 16px; /* Safari iOS иначе зумит страницу при фокусе */
    min-height: 40px;
  }

  /* Кнопки диалога — тач-зона; «Импортировать» тянется на свободную ширину */
  .modal-row .btn {
    min-height: 44px;
  }

  .modal-row .btn.primary {
    flex: 1;
  }

  .curves-list {
    max-height: 40vh;
  }

  .page-toast {
    left: calc(8px + env(safe-area-inset-left, 0px));
    right: calc(8px + env(safe-area-inset-right, 0px));
    max-width: none;
    transform: none;
    border-radius: var(--radius);
  }
}

/* Совсем узкие телефоны (360–400px): форма входа без лишних полей */
@media (max-width: 480px) {
  .login-screen {
    padding: 16px;
  }

  .login-card {
    padding: 18px 16px;
  }
}

/* Анимацию выезда панели гасим, если система просит «меньше движения» */
@media (prefers-reduced-motion: reduce) {
  .tree-pane,
  .tree-backdrop {
    transition: none;
  }
}

/* Светлая тема — только для выдвижной панели, поэтому запрос совмещён с
   брейкпоинтом: на широком экране у .tree-pane тени нет вовсе. */
@media (max-width: 820px) and (prefers-color-scheme: light) {
  .tree-pane {
    box-shadow: 6px 0 24px rgba(27, 32, 40, 0.18);
  }

  .tree-backdrop {
    background: rgba(27, 32, 40, 0.35);
  }
}

=================================================================
   ПАРИТЕТ ВКЛАДОК С ДЕСКТОПОМ
   =========================================================================

   Три новых куска оболочки: серые вкладки-заглушки в ленте, панель
   журнала («Сообщения») и модальные диалоги ленты («Создать проект»,
   «Поделиться…», «История»). Плюс теневые правила для ЯВНО выбранной
   темы: `@media (prefers-color-scheme: light)` выше знает только про
   системную тему, а `data-theme` её перебивает — значит те же блики надо
   повторить (и погасить, когда явно выбрана тёмная на светлой системе). */

/* Заглушка раздела, которого в web нет: вкладка видна и стоит на своём
   месте ряда (порядок совпадает с лентой десктопа), но не нажимается. */
.rb-tab.is-disabled {
  color: var(--text-muted);
  opacity: 0.55;
  cursor: not-allowed;
}

.rb-tab.is-disabled:hover {
  background: transparent;
  color: var(--text-muted);
}

/* ---------------------------------------------------------------- журнал */

/* Панель «Сообщения» — полоса под сценой страницы (не оверлей: журнал
   читают ВМЕСТЕ с планшетом, а не вместо него). */
.msg-panel {
  flex: none;
  display: flex;
  flex-direction: column;
  max-height: 30vh;
  min-height: 120px;
  border-top: 1px solid var(--border);
  background: var(--bg-elevated);
}

.msg-panel[hidden] {
  display: none;
}

.msg-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
}

.msg-title {
  font-weight: 600;
  font-size: 13px;
}

.msg-close {
  margin-left: auto;
}

.msg-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 0;
  font-family: var(--mono);
  font-size: 12px;
}

.msg-empty {
  padding: 10px;
  color: var(--text-muted);
  font-family: var(--font);
}

.msg-empty[hidden] {
  display: none;
}

.msg-row {
  display: flex;
  gap: 10px;
  padding: 2px 10px;
  align-items: baseline;
}

.msg-row:hover {
  background: var(--row-hover);
}

.msg-time {
  color: var(--text-muted);
  flex: none;
}

.msg-level {
  flex: none;
  min-width: 70px;
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.04em;
}

.msg-info .msg-level {
  color: var(--text-muted);
}

.msg-warn .msg-level {
  color: #f0a83c;
}

.msg-error .msg-level {
  color: var(--danger);
}

.msg-source {
  flex: none;
  color: var(--accent);
}

.msg-text {
  flex: 1;
  min-width: 0;
  word-break: break-word;
  font-family: var(--font);
}

/* Дерево, свёрнутое кнопкой «Вид → Дерево проекта» на широком экране:
   колонка грида убирается целиком, вьюер занимает всю ширину. Класс
   ставит js/views/project.js (syncNav) и только вне узкого экрана — там
   дерево и так оверлей, у него свой переключатель. */
.project-page.tree-collapsed .project-layout {
  grid-template-columns: minmax(0, 1fr);
}

.project-page.tree-collapsed .tree-pane {
  display: none;
}

/* Рукоятка — своя колонка грида (см. .project-layout выше); без панели
   рядом её тоже прячем, иначе висела бы посреди схлопнутого вьюера. */
.project-page.tree-collapsed .tree-resize-handle {
  display: none;
}

/* -------------------------------------------------------------- диалоги */

.dlg-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(5, 8, 12, 0.6);
}

.dlg-backdrop[hidden] {
  display: none;
}

.dlg-card {
  width: min(520px, 100%);
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-elevated);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}

.dlg-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
}

.dlg-title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
}

.dlg-close {
  margin-left: auto;
}

.dlg-body {
  padding: 12px 14px;
  overflow-y: auto;
}

.dlg-foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 10px 14px;
  border-top: 1px solid var(--border);
}

.dlg-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Пара [hidden] к display:flex — см. .summary-facts[hidden]. На ней держится
   скрытие полей простого режима газ-лага и ручных полей сдвига в режиме
   «По двум реперам» (фаза AP). */
.dlg-field[hidden] {
  display: none;
}

.dlg-label {
  font-size: 12px;
  color: var(--text-muted);
}

.dlg-input,
.dlg-select {
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg-input);
  color: var(--text);
  font: inherit;
  min-height: 36px;
}

.dlg-row {
  display: flex;
  gap: 8px;
  margin-top: 12px;
  flex-wrap: wrap;
}

.dlg-row .dlg-input {
  flex: 1;
  min-width: 140px;
}

.dlg-hint {
  margin: 0 0 8px;
  color: var(--text-muted);
  font-size: 13px;
}

.dlg-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Пара [hidden] к display:flex — см. .summary-facts[hidden]. Прячет блоки
   колонок и метода газ-лага в режиме «По приборам» (фаза AP). */
.dlg-list[hidden] {
  display: none;
}

.dlg-list-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.dlg-user {
  font-weight: 600;
}

.dlg-role,
.dlg-when,
.dlg-comment {
  color: var(--text-muted);
  font-size: 13px;
}

.dlg-rev {
  font-family: var(--mono);
  flex: none;
  min-width: 70px;
}

.dlg-list-row .btn {
  margin-left: auto;
}

.dlg-error {
  margin: 10px 0 0;
  color: var(--danger);
  font-size: 13px;
}

.dlg-error[hidden] {
  display: none;
}

/* Скрытый <input type=file> для «Импорт .wsp»: элемент обязан остаться в
   потоке (иначе Safari игнорирует программный click), поэтому не display:none. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------- блики/тени при ЯВНО выбранной теме */

:root[data-theme="light"] .rb-panel {
  box-shadow: inset 0 1px 0 rgba(27, 32, 40, 0.04);
}

:root[data-theme="light"] .cmt-popup {
  box-shadow: 0 8px 24px rgba(27, 32, 40, 0.18);
}

:root[data-theme="light"] .modal-card,
:root[data-theme="light"] .dlg-card {
  box-shadow: 0 16px 48px rgba(27, 32, 40, 0.22);
}

:root[data-theme="light"] .modal-backdrop,
:root[data-theme="light"] .dlg-backdrop {
  background: rgba(27, 32, 40, 0.35);
}

:root[data-theme="light"] .page-toast {
  box-shadow: 0 8px 24px rgba(27, 32, 40, 0.18);
}

:root[data-theme="light"] .xs-warn {
  background: rgba(240, 168, 60, 0.18);
}

/* Тёмная тема, выбранная руками на светлой системе: гасим блики светлой
   ветки медиазапроса — иначе поверх тёмного фона легли бы её тени. */
:root[data-theme="dark"] .rb-panel {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}

:root[data-theme="dark"] .modal-backdrop,
:root[data-theme="dark"] .dlg-backdrop {
  background: rgba(5, 8, 12, 0.6);
}

:root[data-theme="dark"] .xs-warn {
  background: rgba(240, 168, 60, 0.12);
}

@media (max-width: 820px) {
  /* На телефоне журнал занимает больше высоты: строк мало, но они длинные */
  .msg-panel {
    max-height: 45vh;
  }

  .dlg-card {
    max-height: 90vh;
  }
}

@media (max-width: 820px) and (prefers-color-scheme: light) {
  .dlg-card {
    box-shadow: 0 16px 48px rgba(27, 32, 40, 0.22);
  }
}
/* --- Фаза V: иконки дерева (tree_icons.js) --- */

/* Почти-чёрные контуры desktop-иконок (#3A3A3A/#444/#222) рисуются в SVG
   через stroke="currentColor" — цвет задаётся здесь тем же механизмом, что
   и остальные токены темы (тёмное значение в :root, светлое — через
   prefers-color-scheme, см. шапку файла): в светлой теме — desktop-цвет,
   в тёмной — светло-серый, чтобы контуры были видимы. */
:root {
  --tree-icon-stroke: #c8c8c8;
}

@media (prefers-color-scheme: light) {
  :root {
    --tree-icon-stroke: #3a3a3a;
  }
}

svg.ti {
  flex: 0 0 auto;
  vertical-align: -3px;
  color: var(--tree-icon-stroke);
}

/* --- Фаза V (V2): структура дерева --- */

/* Информационный лист (порт «инженерные узлы — информационные», план V2):
   раскладка как у .tree-leaf.curve, но это span, а не кнопка — клика нет,
   курсор обычный, hover не подсвечивается. */
.tree-leaf.info {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  font: inherit;
  color: var(--text);
  border-radius: 6px;
  padding: 2px 6px;
  cursor: default;
}

.tree-leaf.info::before {
  content: "•";
  color: var(--text-muted);
}

/* Стабы «Trajectory: —» / «BHA: —» / «Конструкция: —» — приглушённые
   (порт _C_MUTED в desktop project_tree.py). */
.tree-leaf.info.muted {
  color: var(--text-muted);
}

/* Имя проекта в корне — жирным (порт _rebuild: font.setBold). */
.tree-node.project > summary .node-name {
  font-weight: 600;
}

/* Активная скважина: жирный + левая акцентная полоса (порт градиентной
   подсветки _add_well; в web — inset-полоса цветом темы var(--accent)). */
.tree-node > summary.active-well {
  box-shadow: inset 3px 0 0 var(--accent);
}

.tree-node > summary.active-well .node-name,
.tree-node > summary.active-wellbore .node-name {
  font-weight: 600;
}

/* Активный объект группы Trajectories/BHA («✓ » в тексте) — жирный. */
.tree-leaf.info.active-obj .curve-name {
  font-weight: 600;
}

/* --- Фаза V (V4): переключатель Тип/Рейс --------------------------------
   Шапка панели дерева до V4 была видима только на узком экране (блок M2
   выше: .tree-pane-head { display: none } + включение в @media 820px).
   Теперь в ней живёт селектор группировки — шапка нужна всегда; крестик
   закрытия панели по-прежнему только на узком экране (на широком дерево —
   колонка грида, закрывать нечего). Блок идёт ПОСЛЕ правил M2 — каскад
   при равной специфичности выигрывает он. */
.tree-pane-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 2px 8px 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 8px;
}

.tree-close {
  display: none;
}

@media (max-width: 820px) {
  .tree-close {
    display: inline-block;
  }
}

/* Селектор «Тип | Рейс» — компактный вариант .field select (те же
   переменные фона/бордера, меньше поля). */
select.tree-group-mode {
  font: inherit;
  font-size: 12px;
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 3px 6px;
}

select.tree-group-mode:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

/* Узлы режима «Рейс»: активный рейс — жирный (как active-obj у
   инфо-листьев), «Без рейса» — приглушённый, как muted-стабы. */
.tree-node.run.active-obj > summary .node-name {
  font-weight: 600;
}

.tree-node.run.unassigned > summary .node-name {
  color: var(--text-muted);
}

/* --- Фаза V (V7): контекстное меню --------------------------------------
   Компонент js/ui/context_menu.js (меню дерева проекта). Позицию (left/
   top) ставит сам компонент через CSSOM — здесь только внешний вид по
   переменным темы. z-index выше диалогов страницы (.modal-backdrop) —
   меню всегда поверх. */

.cm-menu {
  position: fixed;
  z-index: 240;
  min-width: 190px;
  max-width: 320px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  background: var(--bg-elevated);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
}

.cm-item {
  font: inherit;
  font-size: 13px;
  text-align: left;
  color: var(--text);
  background: transparent;
  border: 0;
  border-radius: 4px;
  padding: 6px 10px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cm-item:hover:not(:disabled),
.cm-item:focus-visible {
  background: var(--accent);
  color: var(--accent-contrast);
  outline: none;
}

.cm-item.danger {
  color: var(--danger);
}

.cm-item.danger:hover:not(:disabled),
.cm-item.danger:focus-visible {
  background: var(--danger);
  color: var(--accent-contrast);
}

.cm-item:disabled,
.cm-item[disabled] {
  color: var(--text-muted);
  cursor: default;
}

.cm-sep {
  height: 1px;
  margin: 4px 6px;
  background: var(--border);
}

/* Скрытый textarea фолбэка «Копировать имя» (execCommand в project.js):
   за экраном, но фокусируемый — select() обязан работать. */
.copy-shim {
  position: fixed;
  left: -1000px;
  top: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* --- Фаза V (V8): dnd кривых --------------------------------------------
   Drag&drop кривой из дерева в трек вьюера (docs/server_phase_v_plan.md,
   пакет V8). Источник — кнопка кривой (project_tree.js: draggable +
   MIME application/x-ws-curve), приёмник — вьюер (js/logview/main.js).
   Подсветка целевого трека — класс .lv-drop-target на .lv-track: сам трек
   стилизуется CSSOM'ом (CSP), но класс из внешнего css легален и
   переживает перерисовки заголовка. outline вместо border — рамка не
   двигает раскладку ленты треков. */

.tree-leaf.curve[draggable="true"] {
  cursor: grab;
}

.tree-leaf.curve.dragging {
  opacity: 0.5;
}

.lv-drop-target {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* --- Фаза V (V9): фильтр и ленивое дерево --------------------------------
   docs/server_phase_v_plan.md, пакет V9. Классы ставит project_tree.js
   (CSSOM там запрещён — только классы из этого файла):
   .tree-hidden    — узлы, скрытые фильтром по имени (setFilter);
   .tree-lazy-note — заглушка «…» ленивого узла до первого раскрытия. */

/* Скрытие фильтром. !important — правило обязано перебивать display
   узлов дерева при любой специфичности (details/summary/flex-листья). */
.tree-hidden {
  display: none !important;
}

/* Заглушка непостроенных детей — как .tree-note, но компактнее: живёт
   внутри .tree-children только до первого раскрытия узла. */
.tree-lazy-note {
  margin: 0 0 0 6px;
  font-size: 12px;
  line-height: 1.2;
  color: var(--text-muted);
}

/* Поле фильтра — компаньон select.tree-group-mode (блок V4 выше): те же
   переменные фона/бордера/фокуса; flex-растяжение занимает свободную
   ширину шапки, min-width: 0 позволяет ужиматься на узкой панели. */
input.tree-filter {
  flex: 1 1 60px;
  min-width: 0;
  font: inherit;
  font-size: 12px;
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 3px 6px;
}

input.tree-filter:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

/* --- Фаза V (V13): пакетный выбор логов -----------------------------------
   docs/server_phase_v_plan.md, пакет V13. .batch-selected ставит
   project.js/project_tree.js на .tree-node.log (Ctrl/Cmd+клик) — левая
   акцентная полоса inset-тенью (не борder, не сдвигает паддинг заголовка)
   + тот же фон, что у :hover, чтобы выбранный узел был виден и без наведения
   мыши, но не путался с .active (подсветка кривой во вьюере, другой смысл). */
.tree-node.log.batch-selected > summary {
  background: var(--row-hover);
  box-shadow: inset 3px 0 0 var(--accent);
}

/* --- Фаза AD (AD2): доступность дерева — ARIA + клавиатура ----------------
   project_tree.js: role=tree/treeitem/group + roving tabindex + стрелки/
   Home/End/Enter/Space. Рамка фокуса — ТОЛЬКО с клавиатуры (:focus-visible,
   не :focus — клик мышью её не оставляет). var(--accent) уже даёт нужный
   цвет в обеих темах (:root — тёмная, @media (prefers-color-scheme: light)
   выше переопределяет её на светлую) — тот же приём, что у соседних
   :focus-visible (.rb-tab, .cm-item, .row-link, select.tree-group-mode,
   input.tree-filter выше): ни один из них не заводит отдельную ветку темы
   под свой outline, полагаясь на переменную. */
.tree-node > summary:focus-visible,
.tree-leaf:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* --- версия сервера в интерфейсе -----------------------------------------
   Строки «Сервер 0.13.0+20» из /health (login.js под карточкой входа,
   projects.js в тулбаре каталога). Обе ставят и .muted (цвет оттуда). */
.login-version {
  margin: 14px 0 0;
  text-align: center;
  font-size: 12px;
}

.catalog-version {
  margin-left: auto; /* прижать к правому краю ряда тулбара */
  font-size: 12px;
  white-space: nowrap;
}
