/* main — общий документ: страница, док и слой окон.

   Здесь только оболочка и геометрия. Цвета, шрифт и палитры —
   в modules/themes/themes.css. */

/* ===== Оболочка документа ===== */

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* ===== Полосы прокрутки =====

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

   Приёма два, на два механизма: scrollbar-width понимают Firefox
   и новые Chromium, ::-webkit-scrollbar — остальные браузеры на WebKit. */
* {
    scrollbar-width: none;
}

*::-webkit-scrollbar {
    display: none;
}

html {
    height: 100%;
}

html,
body {
    margin: 0;
    padding: 0;
}

body.page {
    min-height: 100vh;
}

/* Содержимое страницы не заезжает под док: справа остаётся полоса
   шириной дока плюс зазор.

   Ширина страницы раскладывается на три равных поля и две части:

       поле + содержимое + поле + док + поле

   Поэтому слева от содержимого стоит одно поле, а справа — два:
   второе отделяет содержимое от дока, третье — док от края экрана.
   Слева док не стоит, но поле там то же: так содержимое не липнет
   к краю и окна внутри него встают ровно. */
.page__content {
    min-height: 100vh;
    margin-left: var(--page-gap);
    margin-right: calc(var(--dock-width) + var(--page-gap) * 2);
}

/* ===== Окна страниц =====

   Окно страницы — та же коробка, что у дока: заливка, кольцо акцентом
   на 75% и одна тень на весь сайт. Краски пишутся здесь, а не берутся
   готовым токеном: переменная вычисляется там, где объявлена, — см.
   «Кольцо» в themes.css.

   Размер считается от --window-size — 65% высоты экрана. По ширине окно
   шире этой стороны на 15%, и ещё на 15%; по высоте ниже на 15%.
   Поэтому оно не квадратное, а лежит горизонтально. min() не даёт окну
   вылезти за место для содержимого на узком экране.

   Ширину и высоту страница может переопределить значениями
   --window-width и --window-height: за основу берутся
   --window-base-width и --window-base-height, поэтому считать размеры
   заново не приходится. */
.page-window {
    --fill: var(--color-panel);
    --ring-color: var(--color-border);

    --window-size: 65vh;
    --window-base-width: calc(var(--window-size) * 1.15 * 1.15);
    --window-base-height: calc(var(--window-size) * 0.85);
    --window-width: var(--window-base-width);
    --window-height: var(--window-base-height);

    width: min(var(--window-width), 100%);
    height: var(--window-height);
    margin: 0 auto;
    background: linear-gradient(var(--fill), var(--fill)) padding-box,
                linear-gradient(var(--ring-color), var(--ring-color)) border-box;
    border: 1px solid transparent;
    border-radius: var(--dock-panel-radius);
    box-shadow: var(--shadow-window);
}

/* ===== Затемнение под окном =====

   Пока окно открыто, страница позади приглушается и размывается.
   Слой лежит ниже дока (z-index дока 100) и выше содержимого, поэтому
   перекрывает саму страницу, но не док.

   backdrop-filter в transition намеренно не указан: пока значение
   ползло от blur(0) до blur(10px), браузер пересчитывал размытие всего
   экрана на каждом кадре — анимация подтормаживала. Размытие включается
   мгновенно, а плавность даёт один opacity, который рисует компоузер. */
.page__backdrop {
    position: fixed;
    inset: 0;
    z-index: 90;
    opacity: 0;
    pointer-events: none;
    /* Глушим крепко: при 55% сквозь занавес светились яркие кадры
       страницы — рядом с окном они читались как цветная полоса. */
    background: var(--color-scrim);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    /* Слой вынесен в композицию заранее — и это лечение дрожания
       раскрытия окна. Без хина проявление считает размытие всего
       экрана на каждом кадре: замер даёт 10–12 дрожащих кадров
       за одно открытие. С хинтом размытие считается один раз,
       плавность рисует композиционер, и пропусков не остаётся —
       0 из ~110 кадров, визуально ничего не меняется.
       Цена — один слой на весь экран, а он там и так всегда лежит:
       position: fixed с inset: 0. */
    will-change: opacity;
    transition: opacity 0.32s ease;
}

body.is-panel-open .page__backdrop {
    opacity: 1;
    pointer-events: auto;
}

/* ===== Занавес =====

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

   Переход стоит только на открытии. Закрывается занавес мгновенно:
   он должен быть непрозрачным ещё до того, как уйдёт документ,
   иначе переход начнётся с полупрозрачного экрана. */
.page__veil {
    position: fixed;
    inset: 0;
    z-index: 95;
    background: var(--color-dark);
    opacity: 1;
    pointer-events: auto;
}

.page__veil:not(.is-closed) {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

/* ===== Геометрия дока =====

   Размеры всегда заданы числами (без width/height: auto), поэтому
   переход между состояниями интерполируется плавно. */

:root {
    /* Три поля страницы равны между собой: слева от содержимого,
       между содержимым и доком и справа от дока. Раскладку считает
       .page__content ниже. */
    --page-gap: 12px;

    /* --dock-gap (отступ дока от края экрана) и --dock-pad (отступ внутри
       дока до иконок) намеренно равны, иначе панель выглядит смещённой. */
    --dock-gap: 12px;
    --dock-pad: 12px;
    --dock-radius: 24px;
    --dock-icon: 44px;
    --dock-item-gap: 8px;
    --dock-panel-radius: var(--window-radius);

    /* Ширина выведена из содержимого: pad + icon + pad + рамка 1px
       с каждой стороны. Высоту не считаем числом пунктов — её измеряет
       main.js: у гостя настроек нет, и кнопок в доке меньше. */
    --dock-width: calc(var(--dock-pad) * 2 + var(--dock-icon) + 2px);

    /* Запасное значение на случай, пока скрипт не измерил. */
    --dock-collapsed-height: 174px;

    /* Развёрнутое окно: ширину объявляет панель, высоту измеряет main.js.
       Оба состояния — числа, поэтому переход плавный. */
    --dock-panel-height: 300px;
}

:root {
    /* ===== Ячейка поста =====

       Единица мозаики в ленте и сетки в профиле: карточки постов
       на обеих страницах согласуют с ней размер и скругление,
       поэтому калибр один на весь сайт, а не два расходящихся. */

    /* Высота ряда мозайки: ряд собирается из карточек, вписанных в
       эту высоту, пока не наберёт ширину сетки. По умолчанию — калибр
       ячейки: на ряд приходится столько карточек, сколько раньше
       стояло колонок. */
}

:root {
    /* ===== Строка поиска =====

       Высота бара — одна единица нажатия: строка — тот же док, только
       лежащий сверху и толщиной в один пункт. Значение нужно и самой
       строке, и странице: отступ мозаики держит место под строкой. */
    --searchbar-height: 44px;
}

.dock {
    position: fixed;
    top: 50%;
    right: var(--dock-gap);
    z-index: 100;
    display: block;
    width: var(--dock-width);
    height: var(--dock-collapsed-height);
    overflow: hidden;
    /* Положение задано только transform: right остаётся неизменным
       в обоих состояниях, поэтому переход не вызывает перераскладку.
       Функции в обоих состояниях совпадают по составу — иначе браузер
       интерполировал бы матрицы и переход вышел бы неровным. */
    transform: translateY(-50%) translateX(0);
    border-radius: var(--dock-radius);
    /* Панель непрозрачная, обводка — акцент в покое. Проценты живут
       здесь: тема задаёт только краски. */
    --fill: var(--color-panel);
    --ring-color: var(--color-border);

    background: linear-gradient(var(--fill), var(--fill)) padding-box,
                linear-gradient(var(--ring-color), var(--ring-color)) border-box;
    border: 1px solid transparent;
    /* backdrop-filter здесь намеренно не используется: фон дока
       непрозрачный, так что размытие позади ничего не меняло
       визуально, но заставляло браузер пересчитывать размытие на каждом
       кадре анимации — из-за этого раскрытие формы подтормаживало. */
    /* Одна тень на все окна сайта: свёрнутый док, раскрытое окно и
       intro. Чёрный здесь неизбежен: тень должна быть темнее фона,
       а краска из палитры над фоном того же цвета даёт ноль. */
    box-shadow: var(--shadow-window);
    transition: width 0.3s var(--ease-emphasized),
                height 0.3s var(--ease-emphasized),
                transform 0.3s var(--ease-emphasized),
                border-radius 0.3s ease;
}

/* Раскрытое окно. min() оставляет значения числами, так что переход
   работает даже на узких и низких экранах.

   Центрирование: правый край стоит на --dock-gap от края экрана,
   поэтому сдвигаем окно влево на половину экрана и возвращаем зазор,
   а половина собственной ширины ставит окно по центру. */
.dock.is-expanded {
    width: min(var(--dock-panel-width, 400px), 90vw);
    height: min(var(--dock-panel-height), 86vh);
    transform: translateY(-50%) translateX(calc(-50vw + var(--dock-gap) + 50%));
    border-radius: var(--dock-panel-radius);
}

/* Пока идёт раскрытие или сворачивание, док вынесен в отдельный слой
   заранее: браузер готовит его к анимации до первого кадра, и рывка
   на старте не возникает. Класс снимается по окончании перехода. */
.dock.is-animating {
    will-change: width, height, transform;
}

/* Смена набора пунктов: высота ставится мгновенно. Переход здесь
   вреден — кнопки центрируются в доке, и пока высота едет, они
   смещаются вместе с подсветкой. Класс живёт два кадра. */
.dock.is-reflowing {
    transition: none;
}

/* Мгновенное раскрытие (заставка при входе): окно появляется сразу
   развёрнутым, без растягивания из дока. Переходы гасим у самого дока
   и у его слоёв — навигации и слоя окон. Класс живёт один кадр:
   последующие пересчёты высоты анимируются как обычно. */
.dock.is-instant,
.dock.is-instant .dock__nav,
.dock.is-instant .dock__panel {
    transition: none;
}

/* ===== Навигация ===== */

.dock__nav {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--dock-pad);
    transition: opacity 0.22s ease, transform 0.34s var(--ease-emphasized);
}

/* Подсветка текущей страницы: капля, которая перетекает между пунктами.

   Один элемент вместо фона на каждой кнопке, поэтому движение
   непрерывное. Форму капли задаёт main.js через Web Animations:
   она собирается на исходном пункте, сужается и вытягивается в пути,
   а затем расплёскивается в целевом — как капля, что капает.

   Поэтому перехода здесь нет: движение целиком принадлежит анимации,
   а CSS держит только внешний вид и положение по умолчанию. */
.dock__indicator {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 0;
    width: 44px;
    height: 44px;
    border-radius: 14px;
    /* Капля — акцентная подложка под иконкой выбранной страницы.
       25%: это отметка выбора, ей полагаться на себя заметнее, чем
       наведению на соседние иконки (15%), но идти под белую иконку
       сплошным цветом она не должна. */
    background: color-mix(in srgb, var(--color-accent) 25%, transparent);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

/* Пока не измерено, капля прозрачна: иначе она мелькнула бы в углу. */
.dock__indicator.is-placed {
    opacity: 1;
}

.dock.is-expanded .dock__nav {
    opacity: 0;
    transform: scale(0.82);
    pointer-events: none;
}

/* Список не позиционирован намеренно: тогда offsetLeft и offsetTop
   ссылки считаются от навигации — от той же системы координат,
   в которой лежит абсолютный индикатор. Своя position сбила бы отсчёт. */
.dock__list {
    display: flex;
    flex-direction: column;
    gap: var(--dock-item-gap);
    margin: 0;
    padding: 0;
    list-style: none;
}

.dock__item {
    margin: 0;
    padding: 0;
}

/* Ссылка поднята над каплей, чтобы иконка оставалась видна.
   position: relative делает её positioned, но для offsetParent это
   не важно: родитель — навигация, система координат та же. */
.dock__link {
    position: relative;
    z-index: 1;
    --ib-size: var(--dock-icon);
    --ib-icon: 22px;
}

/* Фон активного пункта рисует индикатор под списком, поэтому
   у самой кнопки его нет: иначе подсветка появилась бы дважды —
   на кнопке мгновенно и на индикаторе с задержкой. */
.dock__link.is-active {
    background: transparent;
}

/* Наведение на выбранный пункт: капля под ним уже светится на 25%,
   а ссылка кладёт сверху ещё 15% — вместе заметно светлее соседних.
   Правило явное: без него наведение приходит из общего .iconbutton
   и держится на порядке подключения файлов. */
.dock__link.is-active:hover {
    background: var(--color-hover);
}

/* Иконки пунктов дока. Выбор идёт по data-icon, а не по view: у гостя
   пункт называется иначе (вместо профиля — вход), и привязка к view
   оставила бы его без иконки. */
/* ===== Слой окон =====

   Лежит поверх навигации absolute и на размер дока не влияет:
   иначе смена содержимого посреди анимации давала бы рывок.
   Отступы задаёт сама панель страницы.

   Прокрутки здесь нет намеренно. Пока док раскрывается, содержимое уже
   полной высоты, а слой ещё маленький — при overflow: auto браузер
   счёл бы это переполнением и разрешил прокрутку на время анимации.
   clip не создаёт область прокрутки вовсе: содержимое просто обрезается,
   а после раскрытия высота совпадает с ним. */
.dock__panel {
    position: absolute;
    inset: 0;
    overflow: hidden;   /* запасной вариант для старых браузеров */
    overflow: clip;
    opacity: 0;
    pointer-events: none;
    transform: translateX(14px);
    transition: opacity 0.3s ease, transform 0.42s var(--ease-emphasized);
}

/* Прокрутка нужна только тогда, когда окно упёрлось в предел высоты
   и содержимое в него не помещается. Класс ставит main.js по замеру. */
.dock__panel.is-scrollable {
    overflow: auto;
}

.dock.is-expanded .dock__panel {
    opacity: 1;
    pointer-events: auto;
    transform: translateX(0);
    transition-delay: 0.1s;
}

/* При сворачивании содержимое гаснет быстрее, чем сжимается док:
   иначе в середине анимации текст успевал бы наехать на края. */
.dock:not(.is-expanded) .dock__panel {
    transition-delay: 0s;
    transition-duration: 0.16s, 0.24s;
}

/* Показано ровно одно окно: то, которое док открыл. Селектор с двумя
   классами-предками, а не просто [hidden]: у правил вида
   .dock-panel[data-panel="feed"] та же специфичность, и они подключены
   позже, поэтому без этого всё три окна раскрывались бы разом. */
.dock__panel .dock-panel[hidden] {
    display: none;
}

/* ===== Переход между страницами =====

   Смену содержимого рисует занавес, а не View Transitions: тот показывал
   два полных снимка одновременно, и на стыке они просвечивали друг
   через друга — отсюда мерцание. Занавес закрывается на старой странице
   и открывается уже на новой, поэтому промежуточного кадра не видно.

   Док здесь только держим на месте: он не участвует в подмене,
   поэтому не мигает вместе со страницей. */
.dock {
    view-transition-name: dock;
}

/* Снимки страницы не анимируем: их полностью закрывает занавес,
   а кросс-фейд двух кадров — это и есть то мерцание, от которого уходим. */
::view-transition-group(root) {
    animation: none;
}

::view-transition-old(root),
::view-transition-new(root) {
    animation: none;
    mix-blend-mode: normal;
}

@media (prefers-reduced-motion: reduce) {
    .dock,
    .dock__nav,
    .dock__panel,
    .dock__link,
    .dock__indicator,
    .page__backdrop {
        transition: none;
    }

    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) {
        animation: none !important;
    }
}

/* ===== Снимок под наведением =====

   Общий приём для картинок, которые можно убрать: контейнер
   .thumb-hoverable, внутри снимок и кнопка .thumb-remove. На наведении
   снимок размывается, поверх проявляется корзина. Так устроены
   скриншоты в обращении и снимки профиля — поэтому правила живут
   здесь, а не в одном из модулей. */

/* Размытие делаем самому снимку, а не слою поверх: вложенный
   backdrop-filter не работает внутри дока. filter указан
   в transition, поэтому эффект плавный. */

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


/* FLIP owns size changes: avoid layout on every transition frame. */
.dock { transition: border-radius .26s ease, background-color .15s ease; }
