/* ═════ Sentos · Модальные окна: каркас, «Новый чат», пересылка и удаление ═════
   Перенесено из прототипа newfront/modal.css + new-chat.css.
   Палитра и общие примитивы — в base.css и app.css. */

/* Фон под окном размывается целиком — вместе с тремя колонками
   приложения. Одного затемнения мало: под ним остаётся читаемый текст,
   и взгляд продолжает цепляться за переписку вместо окна. */
.ui-modal-dim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(0, 0, 0, 0.28);
  backdrop-filter: blur(14px) saturate(120%);
  -webkit-backdrop-filter: blur(14px) saturate(120%);
  animation: dim-in 200ms ease;
}

.dark .ui-modal-dim {
  background: rgba(0, 0, 0, 0.42);
}

.ui-modal-dim.is-closing {
  pointer-events: none;
  animation: dim-out 200ms ease both;
}

.ui-modal {
  position: fixed;
  z-index: 41;
  left: 50%;
  top: 50%;
  display: flex;
  flex-direction: column;
  width: min(430px, calc(100vw - 24px));
  /* Окно никогда не выходит за экран — ни вверх, ни вниз. Из высоты
     вычтены и зазоры по краям, и системные поля iPhone: под чёлкой и
     полоской «домой» окно оказалось бы обрезанным. Центр смещён на
     разницу этих полей, иначе при чёлке сверху окно прижимается к
     нижнему краю. */
  max-height: calc(100dvh - 40px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  margin-top: calc((env(safe-area-inset-top) - env(safe-area-inset-bottom)) / 2);
  border-radius: 30px;
  background: var(--panel);
  overflow: hidden;
  transform: translate(-50%, -50%);
  animation: ui-modal-in 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

.dark .ui-modal {
  background: #1d1d1d;
}

.ui-modal.is-closing {
  pointer-events: none;
  animation: ui-modal-out 160ms ease-in both;
}

/* Окно приходит и уходит масштабом с растворением: чуть уменьшенное
   проявляется до полного размера и так же сворачивается обратно. Ход
   короткий — 5% размера, — поэтому движение читается как появление, а не
   как прыжок. Центр масштабирования — центр окна, поэтому в transform
   рядом с scale обязательно остаётся сдвиг на половину: без него окно
   уедет из центра экрана. */
@keyframes ui-modal-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@keyframes ui-modal-out {
  from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  to { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
}

/* Содержимое отодвинуто от краёв заметнее, чем в панелях приложения:
   окно небольшое и лежит поверх всего, поэтому воздух по краям и держит
   его отдельным предметом, а не куском страницы. Горизонталь общая —
   20 px, — иначе шапка, поля и кнопки встают по разным линиям. */
.ui-modal-head {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
  padding: 14px 14px 8px 22px;
}

.ui-modal-head .screen-title {
  flex: 1;
  min-width: 0;
  font-size: 17px;
}

/* Высота ведётся от старой страницы к новой, поэтому обёртка — точка
   отсчёта для уходящего вида и место, где живёт переход. */
/* Обёртка ужимается, если окно упёрлось в границу экрана: шапка и кнопки
   заданы «не сжимать», поэтому лишнее место отбирается отсюда, а
   содержимое прокручивается внутри страницы. Так ограничение не зависит
   от подобранного вручную числа — оно само считается от высоты экрана. */
.ui-modal-views {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-height: 0;
  overflow: hidden;
  transition: height 300ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ui-modal-page {
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  gap: 6px;
  /* min-height: 0 — иначе страница откажется ужиматься ниже своего
     содержимого и вылезет за край окна вместо того, чтобы прокрутиться. */
  min-height: 0;
  max-height: min(54vh, 430px);
  padding: 2px 20px 16px;
  transition: opacity 220ms ease;
}

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

/* Страницы сменяются по очереди и никогда не стоят в окне вдвоём:
   уходящая гаснет на месте, потом окно меняет высоту под новую, и только
   после этого новая проявляется.

   Наложение здесь пробовали — от него высота дёргалась: пока обе
   страницы в потоке, окно на кадр становится выше их обеих, а уходящая,
   вынутая из потока, роняет высоту раньше, чем приходящая её займёт. */
.ui-modal-page.is-leaving,
.ui-modal-page.is-entering {
  opacity: 0;
  pointer-events: none;
}

/* Уход короче прихода — 170 мс против 220. Ровно столько же ждёт
   group.js, прежде чем убрать страницу: с общей длительностью 220 мс
   уходящую обрывали на девяти десятых пути. */
.ui-modal-page.is-leaving {
  transition-duration: 170ms;
}

.ui-modal-foot {
  display: flex;
  gap: 10px;
  flex: none;
  padding: 8px 20px calc(20px + env(safe-area-inset-bottom));
}

.ui-modal-foot .accent-btn,
.ui-btn-ghost {
  flex: 1;
}

.ui-btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  transition:
    background-color 160ms ease,
    transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ui-btn-ghost.is-pressed:not(:disabled) { transform: scale(0.97); }

@media (hover: hover) {
  .ui-btn-ghost:hover { background: var(--field-strong); }
}


/* ─────────────────────── Телефон ─────────────────────── */

@media (max-width: 760px) {
  .ui-modal {
    width: calc(100vw - 20px);
    border-radius: 26px;
  }

  /* На телефоне потолок страницы снят: сколько влезет в экран, столько и
     покажем, а остальное прокрутится внутри. Полос прокрутки не видно —
     их прячет .scroll-y. */
  .ui-modal-page {
    max-height: none;
  }
}

/* ─────────────────────── Меньше движения ─────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .ui-modal,
  .ui-modal.is-closing,
  .ui-modal-dim,
  .ui-modal-dim.is-closing {
    animation-duration: 1ms;
  }

  .ui-modal-views,
  .ui-modal-page { transition-duration: 1ms; }
}

/* Шапка в два этажа: заголовок шага и подпись под ним. Подпись рабочий
   код переписывает на каждом шаге, поэтому место под неё есть всегда. */
.nc-head-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}

.nc-subtitle {
  overflow: hidden;
  color: var(--muted);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ─────────────── Шаг выбора ─────────────── */

.nc-choice {
  gap: 12px;
  min-height: 62px;
  padding: 10px 14px;
  border-radius: 24px;
  background: var(--field);
}

/* Заливка строки уже занята полем, поэтому отклик — тон темнее */
.nc-choice.is-pressed:not(:disabled) {
  background: var(--field-strong);
}

@media (hover: hover) {
  .nc-choice:hover { background: var(--field-strong); }
}

.nc-choice-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

.nc-choice-title {
  font-size: 15px;
  font-weight: 600;
}

.nc-choice-note {
  color: var(--muted);
  font-size: 12px;
}

.nc-icon {
  background: var(--field-strong);
  color: var(--text-soft);
}

.light .nc-icon {
  background: #e2e2e6;
  color: #4b4b52;
}

.nc-icon.is-accent {
  background: var(--brand-50);
  color: var(--brand-700);
}

.dark .nc-icon.is-accent {
  background: #2a1a16;
  color: var(--accent);
}

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

/* Прокручивается сам список, а не страница: поле поиска и кнопка «Далее»
   должны оставаться на месте, пока человек листает результаты. */
.nc-results {
  flex: 0 1 auto;
  min-height: 0;
  margin: 0 -6px;
  padding: 4px 6px 0;
}

/* Имена классов здесь не наши, а из контракта: после сборки строки людей
   рисует рабочий код (.contact-option с <strong> и <small> внутри), и со
   своими именами оформление не подхватилось бы. */
.contact-option {
  gap: 12px;
  padding: 7px 8px;
  border-radius: 20px;
}

.contact-option > span:not([class]) {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

.contact-option strong {
  overflow: hidden;
  font-size: 15px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.contact-option small {
  overflow: hidden;
  color: var(--muted);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

   Галочка не возникает готовой, а вырастает из уменьшенной вместе с
   заливкой — то же движение, что у иконок в звонке. Поэтому список при
   выборе не перерисовывается: класс переключается на самой строке,
   иначе переходу не от чего отсчитываться. */
.contact-check {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 50%;
  background: var(--field-strong);
  color: var(--accent-fill-text);
  transition: background-color 200ms ease;
}

.contact-check .icon {
  width: 14px;
  height: 14px;
  fill: currentColor;
  opacity: 0;
  transform: scale(0.5);
  transition:
    opacity 160ms ease,
    transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.contact-option.is-selected .contact-check {
  background: var(--accent-fill);
}

.contact-option.is-selected .contact-check .icon {
  opacity: 1;
  transform: none;
}

/* Пустое состояние тоже контрактное: рабочий код пишет сюда
   <div class="list-state small">. */
.ui-modal .list-state {
  padding: 26px 16px;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

/* ─────────────── Выбранные участники ─────────────── */

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

   Место остаётся в разметке всегда, просто нулевой высоты: если убирать
   его совсем, отступ между шапкой и поиском исчезал бы вместе с ним и
   содержимое подпрыгивало бы ещё раз. */
.group-chips-slot {
  overflow: hidden;
  /* Страница расставляет детей через gap, и пустое место всё равно
     забирало бы его себе. Отрицательный отступ съедает ровно этот зазор,
     поэтому пустая полоса не занимает вообще ничего. */
  margin-bottom: -6px;
  transition: height 300ms cubic-bezier(0.22, 1, 0.36, 1);
}

.group-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding-bottom: 12px;
}

/* Пустая полоса не должна занимать даже отступ снизу */
.group-chips:empty {
  padding: 0;
}

.group-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 30px;
  padding: 0 8px 0 12px;
  border: 0;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font: inherit;
  font-size: 12px;
}

/* Крестик голый, без оправы: кружок вокруг него читался бы как вторая
   кнопка внутри метки. */
.group-chip .icon {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  opacity: 0.75;
}

.group-chip.is-pressed:not(:disabled) { transform: scale(0.94); }

/* Метка приходит и уходит масштабом: она появляется не там, где было
   нажатие, и без движения её просто не замечают. Уход короче прихода —
   ровно столько же ждёт new-chat.js, прежде чем убрать метку и повести
   высоту окна к новой. */
.group-chip.is-new {
  animation: nc-chip-in 260ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.group-chip.is-removing {
  pointer-events: none;
  animation: nc-chip-out 180ms ease both;
}

@keyframes nc-chip-in {
  from { opacity: 0; transform: scale(0.8); }
  to { opacity: 1; transform: none; }
}

@keyframes nc-chip-out {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: scale(0.8); }
}

/* ─────────────── Название и вид группы ─────────────── */

.nc-field {
  display: block;
  padding: 11px 16px;
  border-radius: 999px;
  background: var(--field);
  transition: background-color 160ms ease;
}

.nc-field:focus-within {
  background: var(--field-strong);
}

.nc-field input {
  display: block;
  width: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 15px;
}

.nc-field input::placeholder {
  color: var(--muted);
  transition: color 220ms ease;
}

.nc-field input:focus::placeholder {
  color: transparent;
}

.nc-public {
  padding-top: 4px;
}

.nc-public .switch-row {
  border-radius: 22px;
  background: var(--field);
}

/* Подпись объясняет, что даёт переключатель: «публичная» само по себе
   ничего не говорит о том, кто сможет войти. */
.nc-hint {
  margin: 0;
  padding: 8px 16px 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
}

.nc-summary {
  margin: 0;
  padding: 6px 16px 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
}

/* Кнопка шага прижата к низу страницы и не уезжает вместе со списком */
.nc-submit {
  flex: none;
  width: 100%;
  margin-top: 10px;
}

.nc-submit:disabled {
  opacity: 0.5;
}

/* ═════ Дописано при переносе ═════
   Строки, которые рисует рабочий код: люди в поиске, выбор участников
   и чаты в окне пересылки. Имена классов заданы контрактом — свои
   были бы красивее, но после сборки список приходит от сервера. */

.contact-option,
.forward-chat-option {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 8px 12px;
  border: 0;
  border-radius: 20px;
  background: transparent;
  color: inherit;
  text-align: left;
  transition: background-color 160ms ease;
}

@media (hover: hover) {
  .contact-option:hover,
  .forward-chat-option:hover { background: var(--field); }
}

.contact-option.is-pressed:not(:disabled),
.forward-chat-option.is-pressed:not(:disabled) {
  transform: none;
  background: var(--field-strong);
}

.contact-option > span:not(.avatar):not(.contact-check),
.forward-chat-option > span:not(.avatar) {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.contact-option strong,
.forward-chat-option strong {
  overflow: hidden;
  font-size: 15px;
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.contact-option small,
.forward-chat-option small {
  color: var(--muted);
  font-size: 12px;
}

/* Отметка выбора: кружок стоит у каждой строки всегда, просто пустой —
   иначе при первом выборе строка дёргается. Галочка вырастает из
   уменьшенной вместе с заливкой. */
.contact-option.selectable .contact-check {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  margin-left: auto;
  border-radius: 50%;
  background: var(--field-strong);
  color: transparent;
  transition:
    background-color 200ms ease,
    color 200ms ease;
}

.contact-option.selectable .contact-check .icon {
  width: 13px;
  height: 13px;
  transform: scale(0.6);
  transition: transform 260ms var(--ease);
}

.contact-option.is-selected .contact-check {
  background: var(--accent-fill);
  color: var(--accent-fill-text);
}

.contact-option.is-selected .contact-check .icon { transform: none; }

/* Пустое состояние внутри окна: то же, что в списках, только теснее. */
.list-state.small {
  padding: 22px 16px;
  font-size: 12px;
}

/* ─────────────── Окно-подтверждение ───────────────
   Устроено как остальные окна: шапка с вопросом и крестиком, абзац
   пояснения и кнопки внизу — как у окна группы. Отсюда живут удаление
   сообщений (#deleteModal) и отказ от установки (#browserWarnModal).
   Раньше у подтверждения был свой вид — значок в круге и текст по
   центру — и среди прочих окон оно выглядело чужим. Поле слева то же,
   что у заголовка в шапке, — 22 px. */
.confirm-text {
  margin: 0;
  padding: 0 22px 6px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.5;
}

.confirm-actions {
  display: flex;
  gap: 10px;
  width: 100%;
  margin-top: 22px;
}

.confirm-actions .accent-btn,
.confirm-actions .ui-btn-ghost {
  flex: 1;
  min-height: 46px;
}

/* Опасное действие красится своим цветом, а не акцентом: акцент здесь
   у каждой второй кнопки, и «Удалить» им ничем не выделяется. */
.accent-btn.is-danger { background: #cf3d3d; }
.dark .accent-btn.is-danger { background: #8e3434; }

/* Экран установки лежит выше обычных окон (z-index 45), поэтому окно
   поверх него получает свой этаж — иначе оно окажется под экраном. */
.gate-modal { z-index: 46; }
.gate-modal .ui-modal { z-index: 47; }

/* ─────────────── Подписи и многострочные поля ───────────────

   Подпись стоит над значением, как в карточке человека: поле сразу
   говорит, что в нём лежит, и подпись не спорит со значением размером. */

.nc-field-label {
  display: block;
  margin-bottom: 1px;
  color: var(--muted);
  font-size: 12px;
}

/* «О себе» — в несколько строк, поэтому у поля не пилюля, а скруглённый
   прямоугольник: пилюля с двумя строками текста выглядит раздутой. */
.nc-field-area {
  border-radius: 24px;
}

.nc-field textarea {
  display: block;
  width: 100%;
  min-height: 44px;
  max-height: 140px;
  border: 0;
  outline: 0;
  padding: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  line-height: 1.4;
  resize: none;
}

.nc-field textarea::placeholder,
.nc-field input::placeholder {
  color: var(--muted);
}

/* ─────────────── Кадр фотографии профиля ───────────────

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

.crop-modal {
  gap: 14px;
  padding: 0 16px 16px;
}

.crop-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 24px;
  background: #000;
  overflow: hidden;
  cursor: grab;
  /* Жесты разбирает рабочий код: без запрета браузер уводил бы страницу
     прокруткой на первом же движении пальца. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.crop-stage:active { cursor: grabbing; }

.crop-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.crop-mask {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.55);
  pointer-events: none;
}

.crop-zoom {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--muted);
}

.crop-zoom .icon-sm { width: 16px; height: 16px; }

.crop-zoom input[type="range"] {
  flex: 1;
  height: 4px;
  border-radius: 999px;
  background: var(--field-strong);
  outline: 0;
  -webkit-appearance: none;
  appearance: none;
}

.crop-zoom input[type="range"]::-webkit-slider-thumb {
  width: 20px;
  height: 20px;
  border: 0;
  border-radius: 50%;
  background: var(--accent-fill);
  -webkit-appearance: none;
  appearance: none;
}

.crop-zoom input[type="range"]::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border: 0;
  border-radius: 50%;
  background: var(--accent-fill);
}

.crop-modal .confirm-actions { margin: 0; }

/* ───────── Ссылка-приглашение в настройках группы ─────────
   Адрес — моноширинным, чтобы по нему было видно, что это ссылка, а
   кнопки под ним — теми же призрачными, что «Отмена» в окнах. */
.group-link-box {
  display: grid;
  gap: 10px;
  padding: 4px 16px 10px;
}

.group-link-url {
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--field);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  word-break: break-all;
  user-select: all;
}

.group-link-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.group-link-actions .ui-btn-ghost,
.group-link-actions .accent-btn {
  min-height: 38px;
  padding: 0 12px;
  font-size: 14px;
}

.group-link-actions .icon { width: 18px; height: 18px; margin-right: 6px; }
.group-link-actions .ui-btn-ghost.is-danger { color: #cf3d3d; }
.dark .group-link-actions .ui-btn-ghost.is-danger { color: #ff6b6b; }

.group-link-ask p {
  margin: 0 0 8px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.45;
}

.group-link .nc-hint { padding: 0 16px; }

/* ───────── Вступление по ссылке ───────── */
.join-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 4px 22px 10px;
  text-align: center;
}

.join-name { font-size: 18px; font-weight: 600; }
.join-count { color: var(--muted); font-size: 14px; }

.join-about {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.45;
  white-space: pre-line;
}

/* ───────── Кто прочитал ───────── */
.readers-list {
  max-height: min(60vh, 420px);
  padding: 0 8px 12px;
}
