/* Шорты — квадратные видеосообщения со скруглёнными углами.

   Форма задаётся маской, а не кадром: на сервере лежит обычный квадрат
   384×384. Поэтому один и тот же файл показывается любым размером, а
   скругление можно поменять, ничего не перекодируя.

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

:root {
  /* Скругление шорта. Доля стороны, а не пиксели: одна величина
     подходит и записи во весь экран, и маленькому шорту в ленте. */
  --short-radius: 28%;
}

/* ─────────────── Третье лицо кнопки действия ─────────────── */

.action-icons .icon-short {
  opacity: 0;
  transform: scale(.5);
}

.action-button.is-short:not(.is-send) .action-icons .icon-mic {
  opacity: 0;
  transform: scale(.5);
}

.action-button.is-short:not(.is-send) .action-icons .icon-short {
  opacity: 1;
  transform: none;
}

/* Долгое нажатие не должно выделять ничего вокруг: на телефоне это
   выглядит как подсветка половины экрана. */
.action-button,
.short-stage {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* ─────────────────────── Экран записи ───────────────────────

   Поле ввода остаётся выше по слою: во время записи оно показывает
   привычную полосу с таймером, и накрывать его нечем. */
.composer {
  position: relative;
  z-index: 30;
}

.short-stage {
  position: absolute;
  inset: 0;
  z-index: 12;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px 16px 96px;
  opacity: 0;
  transition: opacity 200ms ease;
}

.short-stage[hidden] { display: none; }
.short-stage.visible { opacity: 1; }

/* Переписка под записью не размывается, а гасится плотной заливкой.
   Размытие здесь давало рамки: лента прокручивается и рисуется
   плитками, backdrop-filter размывает каждую отдельно, и на стыках
   проступают контуры пузырей — особенно пока идёт появление сцены и
   размытие само по себе меняется. Плотная заливка обходится без них
   и заодно ближе к правилам проекта, где стеклянных эффектов нет. */
.short-stage-dim {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--page) 94%, transparent);
}

.short-frame {
  --short-size: min(78vw, 46vh, 380px);
  position: relative;
  width: var(--short-size);
  height: var(--short-size);
  transform: scale(.94);
  transition: transform 220ms var(--ease);
}

.short-stage.visible .short-frame { transform: none; }

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

   Кольцо времени сюда не входит: оно выходит за границы кадра на 5 px
   и обрезкой было бы срезано. */
.short-clip {
  position: absolute;
  inset: 0;
  border-radius: var(--short-radius);
  overflow: hidden;
  /* Safari без своего слоя иногда не обрезает потомка с фильтром. */
  transform: translateZ(0);
}

.short-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--field-strong);
  display: block;
  /* Своё лицо привычнее видеть зеркальным. В запись кадр уходит прямым:
     зеркалит только показ. */
  transform: scaleX(-1);
}

.short-frame.is-back .short-video { transform: none; }

/* Стоп-кадр прошлой камеры копируется из холста, а там кадр уже
   зеркальный (передняя камера) или прямой (задняя) — поворачивать его
   ещё раз нечего. */
.short-freeze {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

.short-freeze[hidden] { display: none; }

/* Смена камеры и первый кадр — как в окошке своего изображения на
   экране звонка: не гасим кадр в серое, а размываем его и держим
   размытие переходом. Сколько займёт смена, заранее не знает никто:
   на одном телефоне доля секунды, на другом полторы. Снимается
   размытие тогда же, когда придёт первый кадр новой камеры. */
.short-video,
.short-freeze {
  transition: filter 260ms ease, opacity 420ms ease;
}

.short-frame.is-flipping .short-video,
.short-frame.is-flipping .short-freeze {
  filter: blur(12px) brightness(.78);
}

/* Камера ещё не отдала ни одного кадра: вместо пустого чёрного
   квадрата — размытие и колечко. */
.short-frame.is-loading .short-video {
  filter: blur(12px) brightness(.7);
  opacity: .7;
}

.short-loader {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border: 2px solid rgba(255, 255, 255, .3);
  border-top-color: #fff;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms ease;
}

.short-frame.is-loading .short-loader,
.short-frame.is-flipping .short-loader {
  opacity: 1;
  animation: short-spin 720ms linear infinite;
}

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

/* Рамка времени по краю шорта: доля минуты. pathLength=100 у самой
   фигуры избавляет от счёта периметра со скруглениями. */
.short-ring {
  position: absolute;
  inset: -5px;
  width: calc(100% + 10px);
  height: calc(100% + 10px);
  pointer-events: none;
}

.short-ring-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.4;
  /* Растёт от середины верхней грани, как стрелка часов от двенадцати.
     Путь прямоугольника начинается в левом верхнем углу, поэтому штрих
     сдвинут вперёд на 6,6 сотых периметра - ровно половина верхней
     грани. Скруглять концы нельзя: нулевой штрих рисовался бы точкой. */
  stroke-dasharray: calc(100 * var(--short-progress, 0)) 100;
  stroke-dashoffset: -6.6;
}

/* Последние десять секунд — линия становится тревожной. */
.short-frame.is-ending .short-ring-line { stroke: var(--danger); }

/* У записанного шорта рамка не нужна: он уже не тикает. */
.short-stage.is-draft .short-ring { display: none; }

.short-flip,
.short-play {
  position: absolute;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  color: #fff;
  background: rgba(0, 0, 0, .5);
  cursor: pointer;
  transition: transform 220ms var(--ease), opacity 160ms ease;
}

/* Кнопка стоит под квадратом, по центру, а не в его углу. У шорта
   скругление в 28% — угол настолько круглый, что кнопка внутри него
   всё равно вылезала за край кадра, а чтобы уйти под кривую, ей
   пришлось бы забраться на треть кадра. Под квадратом ей ничего не
   мешает, и она не закрывает лицо. */
.short-flip {
  left: 50%;
  top: calc(100% + 16px);
  width: 46px;
  height: 46px;
  transform: translateX(-50%);
}

.short-flip .icon {
  width: 22px;
  height: 22px;
  fill: currentColor;
}

/* Сдвиг на половину ширины обязан стоять рядом с любым другим
   превращением: без него кнопка уедет вправо от центра. */
.short-flip:active { transform: translateX(-50%) scale(.9); }

/* Нажатие ставит общий класс is-pressed, и его правило в base.css
   сильнее :active — без повтора сдвига кнопка при касании уезжала вправо. */
.short-flip.is-pressed:not(:disabled) { transform: translateX(-50%) scale(.9); }

.short-frame.is-flipping .short-flip {
  transform: translateX(-50%) rotate(180deg);
}

/* Одна камера — переворачивать нечего, кнопку прячем совсем. */
.short-stage.no-flip .short-flip,
.short-stage.is-draft .short-flip { display: none; }

/* Просмотр записанного: кнопка во весь кадр, при игре уходит.
   Размытия у неё нет: она лежит поверх видео, и backdrop-filter
   размывал сам записанный кадр — от этого шорт в черновике выглядел
   не в фокусе, а по краю кнопки шла заметная рамка. */
.short-play {
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: var(--short-radius);
  background: rgba(0, 0, 0, .18);
}

.short-play[hidden] { display: none; }

.short-play .icon {
  width: 42px;
  height: 42px;
  fill: currentColor;
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, .5));
}

.short-stage.is-playing .short-play {
  opacity: 0;
  background: none;
}

/* ─────────────────── Шорт в переписке ───────────────────

   Пузыря нет: фон и отступы сняты, форма сообщения — сам шорт.
   Время и галочки уходят вбок, под шорт их не подложить. */
.message.is-short {
  padding: 0;
  background: none;
  box-shadow: none;
  /* Рамка проигранного выступает за шорт на 4 точки. Пузырь обрезает
     всё, что за краем, — у шорта пузыря нет, и срезалась бы рамка. */
  overflow: visible;
}

/* Время отправки — пилюлей справа внизу, как у фото и видео. Отступ в
   процентах: угол шорта скруглён на 28%, и у самого края пилюля легла бы
   на пустую кривую. */
.message.is-short .message-meta.on-media {
  right: 16%;
  bottom: 8px;
  pointer-events: none;
}

.short-message {
  --short-play-progress: 0;
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

.short-media {
  position: relative;
  width: 180px;
  height: 180px;
  flex: none;
}

/* Играющий шорт крупнее, как в Telegram: смотрят его, а не соседей. На
   паузе и по окончании он возвращается к своему размеру. */
.short-message.is-playing .short-media {
  width: 260px;
  height: 260px;
}

.short-asset {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--short-radius);
  background: var(--field-strong);
  display: block;
}

/* Рамка проигранного. У нетронутого шорта её не видно — она только
   отвлекала бы от картинки. */
.short-progress {
  position: absolute;
  inset: -4px;
  width: calc(100% + 8px);
  height: calc(100% + 8px);
  pointer-events: none;
  opacity: 0;
  transition: opacity 180ms ease;
}

.short-message.is-playing .short-progress,
.short-message.is-started .short-progress { opacity: 1; }

.short-progress-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.6;
  /* Отсчёт от середины верхней грани - как у рамки записи. */
  stroke-dasharray: calc(100 * var(--short-play-progress)) 100;
  stroke-dashoffset: -6.6;
}

.short-open {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--short-radius);
  background: rgba(0, 0, 0, .2);
  color: #fff;
  cursor: pointer;
  transition: opacity 200ms ease, background 200ms ease;
}

.short-open .icon {
  width: 34px;
  height: 34px;
  fill: currentColor;
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, .45));
}

.short-message.is-playing .short-open {
  opacity: 0;
  background: none;
}

/* Только там, где наведение настоящее: на iPhone :hover залипает после
   касания, и кнопка висела поверх играющего шорта. */
@media (hover: hover) {
  .short-message.is-playing .short-media:hover .short-open { opacity: 1; }
}

/* Длительность — такой же пилюлей слева внизу, в пару к времени справа. */
.short-duration {
  position: absolute;
  left: 16%;
  bottom: 8px;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.42);
  color: #fff;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transform: translateY(2px);
  pointer-events: none;
}

/* Пока сервер пережимает запись, показываем её же под пульсацией:
   место в ленте занято, и видно, что идёт работа. */
.short-processing .short-asset { filter: brightness(.7) saturate(.6); }

.short-processing .short-open {
  background: rgba(0, 0, 0, .38);
}

/* Колечко загрузки на самом шорте — вместо надписи сбоку. */
.short-spinner {
  width: 34px;
  height: 34px;
  border: 2.5px solid rgba(255, 255, 255, .9);
  border-right-color: transparent;
  border-radius: 50%;
  animation: mediaSpin 900ms linear infinite;
}


@media (max-width: 760px) {
  .short-media { width: 150px; height: 150px; }
  .short-message.is-playing .short-media { width: min(250px, 68vw); height: min(250px, 68vw); }
  .short-open .icon { width: 30px; height: 30px; }
}

/* ─────────────── Черновик шорта в поле ввода ───────────────

   Полоса та же, что у голосового, только волны в ней нет: у шорта
   слушать нечего — его смотрят выше, в самой картинке. */
.voice-state-draft.is-short .voice-bar-wave { display: none; }

.voice-state-draft.is-short .voice-bar-label {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  color: var(--text-soft);
}

.voice-state-draft:not(.is-short) .voice-bar-label { display: none; }

/* ─────────────── Выбор камеры в настройках ───────────────

   Галочка стоит только у выбранной строки: список короткий, отдельная
   разметка под переключатель ему не нужна. */
.facing-row .facing-check {
  width: 20px;
  height: 20px;
  fill: currentColor;
  color: var(--accent);
  opacity: 0;
  transition: opacity 160ms ease;
}

.facing-row[aria-checked="true"] .facing-check { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .short-stage,
  .short-frame,
  .short-processing .short-open { transition: none; animation: none; }
}

/* Записанное показываем прямым: зеркалить нужно только живую камеру. */
.short-stage.is-draft .short-video { transform: none; }

/* В полосе черновика у шорта нет проигрывателя: смотреть его надо в
   картинке над полем ввода, там своя крупная кнопка. */
.voice-state-draft.is-short .voice-bar-play { display: none; }

/* Пока идёт запись, выделять нечего: долгое нажатие иначе подсвечивает
   пол-экрана синим — так телефон понимает удержание пальца. */
.messenger.recording,
.messenger.recording * {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Полоса записи шорта: та же, что у голосового, но без эквалайзера.
   Рисовать уровень звука там, где и так идут камера, холст и кодек, —
   значит отнимать кадры у самой записи. */
.voice-state-live.is-short .voice-bar-wave { display: none; }

.voice-state-live.is-short .voice-bar-label {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  color: var(--text-soft);
}

.voice-state-live:not(.is-short) .voice-bar-label { display: none; }

/* ─────────────── Переключатель вида записи ───────────────

   Стоит у поля ввода и показывает не текущий вид, а тот, на который
   переключит: так понятнее, что случится по нажатию. Сама круглая
   кнопка при этом показывает, что запишется сейчас. */
.composer-mode[hidden] { display: none; }

/* Размер — как у кнопки эмодзи рядом, а не как у кнопок снаружи поля.
   С 42 px эта кнопка распирала оболочку поля до 46, и все круглые
   кнопки вокруг (42) оказывались на 2 px ниже её центра: они выровнены
   по нижнему краю, а не по середине. В макете внутри поля стоит одна
   кнопка на 38, и оболочка там 43. */
.composer-mode {
  width: 38px;
  height: 38px;
}

/* 22 px, а не 21: внутри кнопки отступ должен быть целым числом
   пикселей, иначе иконка дёргается в конце своей анимации — подробнее
   в composer.css у .action-icons. */
.composer-mode .mode-icons {
  position: relative;
  display: block;
  width: 22px;
  height: 22px;
}

.composer-mode .mode-icons .icon {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  fill: currentColor;
  transition: opacity 160ms ease, transform 220ms var(--ease);
}

/* Пишем голосовое — предлагаем шорт. */
.composer-mode .icon-mic {
  opacity: 0;
  transform: scale(.6);
}

.composer-mode.is-short .icon-mic {
  opacity: 1;
  transform: none;
}

.composer-mode.is-short .icon-short {
  opacity: 0;
  transform: scale(.6);
}
