  /* ==========================================================
     7. ПРОФИЛЬ
     ========================================================== */

  .profile-list { display: flex; flex-direction: column; gap: 6px; }

  .profile-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    padding: 8px 10px;
    border: 1px solid var(--line);
    border-left: 3px solid var(--line-strong);
    background: var(--surface-1);
  }

  .profile-row.is-active { border-color: var(--line-strong); border-left-color: var(--accent); background: var(--surface-2); }

  .profile-row__meta { color: var(--text-dim); font-size: 12px; }
  .profile-row__level { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; }

  .inline-edit {
    background: transparent;
    border: 1px solid transparent;
    color: var(--text);
    font-size: 14px;
    height: var(--chip-h);
    padding: 0 6px;
  }

  .inline-edit:hover { border-color: var(--line); background: var(--surface-2); }
  .inline-edit:focus-visible { border-color: var(--accent); background: var(--surface-2); }

  .inline-edit--name { flex: 1 1 140px; min-width: 0; font-family: var(--font-display); font-weight: 600; letter-spacing: .02em; }
  .inline-edit--level { width: 56px; font-variant-numeric: tabular-nums; }
  /* Уровень не отмечен: поле шире (влезает «не указан») и пунктиром —
     от уровня зависят пороги квестов, пустоту надо видеть. */
  .inline-edit--level.is-empty { width: 96px; border-style: dashed; }

  .profile-row__actions { display: flex; gap: var(--sp-2); margin-left: auto; flex-shrink: 0; align-items: center; }

  .profile-form {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    align-items: flex-end;
    margin-top: var(--sp-3);
  }

  .profile-form .field { min-width: 140px; }

  /* Торговцы стоят колонками, а не одним столбцом во всю ширину.
     Оболочка широкая (--shell: 1760px), и в один столбец имя жалось к
     левому краю, а кнопки уровней — к правому: глаз терял строку, пока
     ехал от «Барахольщика» до его же кнопок. Колонка шириной 400px —
     это ровно та ширина, в которую влезает и строка «имя + уровни +
     реп.», и лестница порогов под ней, поэтому расстояние от имени до
     кнопок теперь не зависит от ширины экрана вообще: 1728px дают 4
     колонки, 1248px (минимум, который поддерживаем) — 3, узкий экран —
     одну. min(100%, 400px) вместо голых 400px: иначе на экране уже
     400px единственная колонка вылезала бы за него. */
  .loyalty-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 400px), 1fr));
    gap: 0 var(--sp-5);
    margin-top: var(--sp-2);
  }

  /* Строка торговца несёт теперь три поля вместо одного (уровень,
     репутация, лестница порогов). Flex с переносом, а не grid с
     фиксированными колонками: на 400px порогов в одну строку с именем
     не разместить, а обрезать их нельзя — ради них строка и заведена. */
  .loyalty-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px var(--sp-2);
    padding: 6px 0;
    border-bottom: 1px solid var(--line);
  }

  /* Имя НЕ растягивается (было flex: 1 1 130px) — оно и раздвигало имя
     и кнопки на всю ширину экрана. Фиксированная колонка заодно
     выстраивает кнопки уровней всех торговцев по одной вертикали:
     сравнивать отметки глазом проще, чем когда ряды кнопок пляшут за
     длиной имени. overflow-wrap — на случай имени длиннее колонки:
     пусть переносится, но не растягивает её. */
  .loyalty-row__name {
    color: var(--text);
    font-size: 13px;
    flex: 0 0 132px;
    min-width: 0;
    overflow-wrap: anywhere;
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }

  .loyalty-rep { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
  .loyalty-rep__label { font-size: 11px; color: var(--text-faint); font-family: var(--font-display); letter-spacing: .08em; text-transform: uppercase; }

  .loyalty-rep input {
    width: 62px;
    height: 26px;
    background: var(--surface-1);
    border: 1px solid var(--line);
    color: var(--text);
    font-size: 12px;
    padding: 0 4px;
    text-align: right;
  }

  .loyalty-rep input:hover { border-color: var(--line-strong); }

  /* Лестница порогов самого торговца. Занимает всю ширину строки —
     это справка, а не поле ввода, и ей нечего делить место с кнопками. */
  .loyalty-levels { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 3px; }

  .loy-step {
    font-family: var(--font-display);
    font-size: 11px;
    color: var(--text-faint);
    border: 1px solid var(--line);
    padding: 1px 5px;
    white-space: nowrap;
  }

  /* Достигнутые ступени гасим, ближайшую недостающую — подсвечиваем:
     владелец смотрит сюда ровно с одним вопросом «что дальше». */
  .loy-step.is-done { opacity: .45; }
  .loy-step.is-next { color: var(--accent); border-color: var(--accent); }

  .level-btns { display: flex; gap: 3px; flex-shrink: 0; }

  .level-btn {
    background: var(--surface-1);
    border: 1px solid var(--line);
    color: var(--text-dim);
    width: 26px;
    height: 26px;
    cursor: pointer;
    font-family: var(--font-display);
    font-size: 12px;
  }

  .level-btn:hover { border-color: var(--line-strong); color: var(--text); }
  .level-btn.active { background: var(--surface-3); color: var(--accent); border-color: var(--accent); }

  /* Вход и пользователи (21.09.2026, авторизация). Новых цветов нет:
     форма входа — те же .field, что у нового профиля, таблица
     пользователей — общие table/thead/tbody из барахолки. Здесь только
     раскладка, которой у существующих классов не было. */
  .login-form {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: flex-end;
    gap: var(--sp-3);
    margin: 0 auto var(--sp-3);
    text-align: left;
  }

  .login-form .field { min-width: 180px; }
  .login-error { display: block; margin: 0; }

  .users-table tr.is-off td { color: var(--text-faint); }
  .users-table .inline-edit { width: 220px; border-color: var(--line); background: var(--surface-1); }
  .users-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
  .users-actions .text-btn:disabled { opacity: .4; cursor: not-allowed; }

  /* Флажок «администратор» в форме создания. Не .field: тот задаёт полю
     высоту контрола и фон, а флажку они ни к чему. */
  .users-admin-check {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--ctrl-h);
    color: var(--text-dim);
    font-size: 13px;
    cursor: pointer;
    user-select: none;
  }


  /* ---------- настройки интерфейса ----------
     Кнопка-переключатель и объяснение к нему рядом, а не под ним: без
     подписи «широкий план рейда» это загадка, а подпись из двух строк
     под кнопкой читалась бы как заголовок следующего блока. Ширина
     объяснения ограничена по той же причине, что у .plan-note: строка
     в 1700 точек не читается.
     Сам переключатель — обычный .toggle-btn: это тот же знак, что у
     «от респа ЧВК» на плане, и второй вид для того же понятия
     («включено / выключено») был бы разнобоем. */
  .ui-setting {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-2) var(--sp-4);
  }

  .ui-setting__note {
    flex: 1 1 320px;
    margin: 0;
    max-width: 78ch;
    color: var(--text-dim);
    font-size: 13px;
    line-height: 1.5;
  }

  /* ---------- журнал статусов ----------
     Строка — сетка, а не flex: «когда» и «откуда» держат одну ширину по
     всему списку, иначе колонка стрелок гуляла бы от строки к строке
     вслед за длиной имени квеста. Имя квеста обрезается многоточием,
     полное — в title. */
  .history-list { display: flex; flex-direction: column; }
  .history-row {
    display: grid;
    grid-template-columns: 9em minmax(12em, 1fr) auto minmax(8em, auto) auto;
    gap: var(--sp-2) var(--sp-3);
    align-items: baseline;
    padding: 4px 0;
    border-bottom: 1px solid var(--line);
    font-size: 13px;
  }
  .history-row__when, .history-row__source { color: var(--text-dim); white-space: nowrap; }
  .history-row__task { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .history-row__change { white-space: nowrap; }
  .history-row__arrow { margin: 0 var(--sp-1); color: var(--text-dim); }
  .history-row__status.is-none { color: var(--text-dim); font-style: italic; }

  /* ---------- корзина и перенос (миграция 026) ----------
     Удалённый профиль — та же строка, что живой, только приглушённая и с
     красной кромкой: второй вид строки для того же понятия был бы
     разнобоем. Имя — не поле ввода: править удалённый нельзя. */
  .profile-row.is-deleted { opacity: .75; border-left-color: var(--danger); }
  .profile-row__name { flex: 1 1 140px; min-width: 0; font-family: var(--font-display); font-weight: 600; letter-spacing: .02em; }
  .transfer { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-2); }
  .transfer__report { margin: 0; padding-left: 18px; font-size: 13px; color: var(--text-dim); line-height: 1.5; }
  .transfer__report .is-warn { color: var(--danger); }
  .transfer__actions { display: flex; gap: var(--sp-3); align-items: center; }

  /* ---------- сессии (28.09.2026, блок C) ----------
     Таблица — общие table/thead/tbody из барахолки. Своя строка
     подсвечена фоном: рядом с ней кнопка «выйти», а не «закрыть», и
     перепутать их — выкинуть себя. */
  .sessions-table tr.is-current td { background: var(--surface-1); }
  .sessions-table .text-btn:disabled,
  .sessions-bulk .text-btn:disabled { opacity: .4; cursor: not-allowed; }
  .sessions-bulk { margin: 0 0 var(--sp-2); }

  /* ---------- сбор данных (28.09.2026, блок C) ----------
     Метка строки — полоса слева, как у .profile-row: закрашенная
     целиком строка журнала в 30 строк рябила бы. Цвет — только у слова
     статуса, остальной текст читается как обычно. Порога «устарело»
     нет, поэтому и цвета для него нет. */
  .sync-table tr.is-error td:first-child,
  .sync-runs tr.is-error td:first-child { box-shadow: inset 3px 0 0 var(--danger); }
  .sync-table tr.is-warn td:first-child,
  .sync-runs tr.is-warn td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
  .sync-table tr.is-error .sync-status,
  .sync-runs tr.is-error .sync-status { color: var(--danger); }
  .sync-table tr.is-warn .sync-status,
  .sync-runs tr.is-warn .sync-status { color: var(--accent); }

  /* Текст аварии бывает сотнями килобайт SQL (сервер режет до тысячи
     символов, клиент — до 160 в строке); без переноса по любому месту
     одна ячейка растянула бы таблицу на экраны вправо. */
  .sync-runs__error {
    max-width: 60ch;
    color: var(--text-dim);
    font-size: 12px;
    overflow-wrap: anywhere;
  }

  /* ---------- архив профиля (28.09.2026, блок A) ----------
     Та же строка, только приглушённая: отдельный вид для того же
     понятия был бы разнобоем. Выбрать архивный профиль можно — это
     не корзина, прогресс его виден и правится. */
  .profile-row.is-archived { opacity: .7; }
