/*
    Знак ожидания: заглушки на месте будущего содержимого и полоса под шапкой.

    Страницу рисует шаблонизатор, а её содержимое приезжает вторым запросом и
    появляется через полсекунды и позже. Всё это время человек смотрит на пустое
    место — и пустое место одинаково выглядит и когда «едет», и когда «никого
    нет», и когда сеть отвалилась. На медленной связи раздел читается как
    сломанный.

    Средства два, и оба общие, а не по копии на страницу.

    Заглушка — там, где форма будущего содержимого известна: карточка-призрак
    занимает ровно столько же места, сколько займёт настоящая, и подмена не
    дёргает страницу под пальцем.

    Полоса — там, где заглушке взяться неоткуда: отправка формы, переключение
    вкладок, обновление счётчиков. Она отвечает на один вопрос: «вообще
    что-нибудь происходит?»

    Цвета и повадки взяты у знака работы в кнопках (.btn-spin, style.css) и у
    полосы под лентой (.strip-progress, card-strip.css): фиолетовый градиент
    сервиса, ничего нового не выдумываем.
*/

/* ===== Гость =====

   Данные раздела лежат под токеном, и гостю вместо карточек придёт панель
   «войдите». Нарисованная заранее заглушка мелькнула бы перед ней карточками,
   которых у него нет и не будет. Решаем это до первой отрисовки и без запроса:
   js/loading.js читает ключ из хранилища синхронно — тем же приёмом, каким
   меню в parts/tabbar.ftlh решает, показывать ли себя. */
.sk-card,
.sk-row,
.sk-line,
.sk-shell,
.sk-shell-flex { display: none; }

html.has-token .sk-shell { display: block; }
/* Оболочка, устроенная внутри колонкой. Экран разговора занимает ровно окно:
   шапка, лента реплик и поле ввода делят его высоту флексом, и блок вместо
   колонки распустил бы ленту на всю переписку — поле ввода уехало бы под низ
   страницы. Правило то же самое, отличается только display. */
html.has-token .sk-shell-flex { display: flex; }
html.has-token .sk-card { display: flex; }
html.has-token .sk-row,
html.has-token .sk-line { display: block; }

/* ===== Переливание =====

   Мягкая волна слева направо, а не мигание: мигающее пятно тянет взгляд к
   себе, а человек в этот момент читает заголовок и ждёт содержимое. Приём тот
   же, что у тасующейся колоды (.tarot-wait-shine): отблеск едет поверх бледной
   подложки. */
.sk-glow {
    position: relative;
    overflow: hidden;
    border-radius: 8px;
    background-color: rgba(124, 58, 237, .10);
    background-image: none;
}

.sk-glow::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(105deg,
        transparent 35%, rgba(255, 255, 255, .72) 50%, transparent 65%);
    transform: translateX(-120%);
    animation: sk-shine 1.6s ease-in-out infinite;
}

@keyframes sk-shine {
    0% { transform: translateX(-120%); }
    55%, 100% { transform: translateX(120%); }
}

/* Просившим не двигать картинку остаётся неподвижная заглушка: место она
   держит по-прежнему, и «здесь что-то будет» читается по ней самой. Гасить её
   вместе с движением нельзя — тогда вернётся ровно та пустота, ради которой
   всё и делалось. */
@media (prefers-reduced-motion: reduce) {
    .sk-glow::after { animation: none; opacity: 0; }
}

/* ===== Карточка-призрак =====

   Геометрия у неё не своя: .sk-card и его внутренности перечислены прямо в
   правилах настоящей карточки (app.css), поэтому разъехаться на пиксель они
   не могут. Здесь остаются только отличия. */
.sk-card {
    /* Нажимать призрака не на что: рука с указателем ищет анкету, которой
       ещё нет. */
    cursor: default;
}

/* Полоски под имя и подпись: во всю ширину они читались бы заливкой, а не
   строкой текста. Высоту держит неразрывный пробел внутри — она равна высоте
   настоящей строки, и от ширины не зависит. */
.sk-name { width: 68%; }
.sk-meta { width: 46%; }

/* Тень фирменной кнопки под заглушкой выглядит как настоящая кнопка, которую
   почему-то нельзя нажать. */
.sk-open .btn { box-shadow: none; }

/* Ширина в ленте. Правило .app-strip .app-card (card-strip.css) задано по
   классу настоящей карточки, и призрак под него не попадает. Числа повторены
   ровно те же: разъехавшись, они заставят полосу прокрутки прыгнуть в момент
   подмены — прямо под пальцем. */
.app-strip .sk-card { flex: 0 0 clamp(140px, 40vw, 190px); }

@media (min-width: 761px) {
    .app-strip .sk-card { flex-basis: 210px; }
}

/* ===== Строка и полоска =====

   Списками устроена половина кабинета: история, расклады, разговоры. Высота
   задаётся страницей (--sk-h), потому что строка у каждого списка своя, а
   заглушка обязана занять её место точно. */
.sk-row {
    height: var(--sk-h, 56px);
    margin-bottom: var(--sk-gap, 10px);
    border-radius: 14px;
}

/* Абзац, который ещё не пришёл: несколько строк разной длины. Последняя
   короче — так выглядит настоящий текст, а ровный прямоугольник читается как
   пустое поле ввода. */
.sk-line { height: 1em; margin: .55rem 0; border-radius: 6px; }
.sk-line-last { width: 62%; }

/* ===== Полоса сверху =====

   Висит абсолютом по нижней кромке шапки: занимающая высоту в потоке отняла бы
   её у содержимого и сдвинула бы страницу вниз в момент появления. На странице
   разговора это единственный работающий вариант — там лента реплик считает
   высоту от экрана. */
.load-bar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    z-index: 1;
    pointer-events: none;
    opacity: 0;
    background: linear-gradient(120deg, #7C3AED, #DB2777);
    transform: scaleX(0);
    transform-origin: left center;
}

/* Полоса ползёт к трём четвертям и там замедляется: сколько осталось ждать, мы
   не знаем, а дошедшая до края и замершая полоса врёт про «вот-вот». */
.load-bar.is-on {
    opacity: 1;
    transform: scaleX(.72);
    transition: transform 8s cubic-bezier(0, .7, .2, 1), opacity .1s ease;
}

/* Ответ пришёл — доводим до конца и гасим. Полоса, исчезнувшая с середины,
   читается как оборванный запрос, а не как выполненный. */
.load-bar.is-done {
    transform: scaleX(1);
    opacity: 0;
    transition: transform .2s ease-out, opacity .3s ease .1s;
}

/* Страница без тёмной шапки: место у полосы то же — верхняя кромка экрана. */
.load-bar-loose {
    position: fixed;
    top: 0;
    bottom: auto;
}

/* Ползущая полоса — то же движение, что и всё остальное. Оставляем сам знак,
   убираем ход: правило здесь такое же, как у .btn-spin, — успокоить, а не
   выключить. */
@media (prefers-reduced-motion: reduce) {
    .load-bar.is-on { transform: scaleX(1); transition: opacity .12s ease; }
    .load-bar.is-done { transition: opacity .3s ease; }
}
