/* profile — страница профиля.

   Страница — мозаика, как в ленте: сверху карточка профиля — окно
   .page-window, внутри которого только шапка (снимок, полоса, круг),
   ниже — сетка карточек постов. Листается всё вместе одним прокрутом:
   страница стоит на месте, а мозаика уезжает под неё.

   Поля сверху и снизу — --page-gap, тот же зазор, что между доком
   и краем экрана. Слева такой зазор уже даёт .page__content, справа —
   полоса дока, поэтому по бокам окно дополнительно не отступает. */

.profile {
    display: flex;
    flex-direction: column;
    height: 100vh;
    padding: var(--page-gap) 0;
}

/* Мозаика занимает отведённое место целиком и листается внутри него.
   Внутри — карточка профиля и сетка постов, разделённые тем же
   зазором, что карточки между собой. */
.profile__mosaic {
    flex: 1;
    min-height: 0;
    overflow-y: auto;

    display: flex;
    flex-direction: column;
    gap: var(--page-gap);
}

/* Карточка профиля — окно во всю ширину мозаики. Высота — ровно та,
   что была у шапки, когда окно занимало всё место страницы: сорок
   процентов от него. Верхняя часть не меняет размера от того, что
   стала целой карточкой. Прокрутки внутри нет: место под посты
   живёт вне окна. */
.profile__window {
    flex: none;
    width: 100%;
    height: calc((100vh - var(--page-gap) * 2) * 0.4);
    margin: 0;

    /* Без тени: окно в старой раскладке шло во всю высоту страницы, и
       тень пряталась у его нижнего края. Теперь под карточкой зазор
       с сеткой постов — тень висела бы в нём тёмными полосами вокруг
       первого ряда. Глубину даёт рамка, тени карточке не нужно. */
    box-shadow: none;

    display: flex;
    flex-direction: column;
}

/* Без снимка шапка выше содержимого полосы не нужна — и карточке
   наименьшая высота ни к чему: пусть растёт по содержимому, как
   растёт сама полоса. */
.profile__window:has(.profile__head--bare) {
    height: auto;
}

/* Долю тёмной полосы держим числом: от неё считается не только
   раскладка шапки, но и место плашки правки кадра — та живёт в слое,
   перенесённом в body, куда доходят только корневые переменные. */
:root {
    --profile-band-share: 0.36;
}

/* Шапка — всё окно целиком: верхняя часть профиля и есть карточка.
   Внутри она делится на светлую часть и полосу: 64 и 36. Долю полосы
   держим переменной — от неё же считается место круга.

   Радиус и обрезка — рамка клипа для блика загрузки: пока обложка
   едет, по шапке ходит матовая полоса (см. ниже), и слой блика должен
   резаться по краю карточки. Радиус — тот же, что у снимка шапки,
   на пиксель меньше окна, на толщину его рамки. Обрезка не мешает
   правке: плашку правки кадра скрипт переносит в body, подсказка
   города — инлайн-хинт, инструменты покраски и полоса со своими
   углами лежат внутри шапки. isolate держит блик в контексте шапки:
   без него слой с отрицательным z-index уводился бы под заливку
   карточки окна и стал бы невидимым. */
.profile__head {
    --profile-band: calc(var(--profile-band-share) * 100%);
    --avatar-size: 220px;
    --avatar-inset: 30px;
    --avatar-gap: 10px;

    flex: 1;
    display: flex;
    flex-direction: column;
    position: relative;
    isolation: isolate;
    overflow: hidden;
    border-radius: calc(var(--dock-panel-radius) - 1px);
}

/* Фото шапки — фон светлой части. Лежит первым слоем: полоса
   закрывает его низ, круг и подписи — верх. Снимок закрывает всю
   карточку, поэтому углы у него все четыре — те же, что у окна,
   только на пиксель меньше, на толщину его рамки: прямой нижний
   угол вылезал бы из-под полосы поверх рамки и торчал за
   скруглением карточки. */
.profile__cover {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: calc(var(--dock-panel-radius) - 1px);
}

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

   Рисунок — сама картинка, а не маска. Так нужно затем, что маску
   браузер накладывает после фильтра: у маски размывать нечего — заливка
   ровная, а форму держит она сама и ложится последней. Картинку же
   фильтр видит, поэтому размытие на наведении (ниже) читается.

   Рисунок лежит первым слоем: снимок, полоса и круг закрывают его.
   Углы у слоя все четыре, на пиксель меньше окна: слой закрывает
   карточку целиком, и прямые нижние углы вылезали бы из-под полосы.
   У гостя без снимка светлой части нет вовсе — там и рисунка нет. */
.profile__head:not(.profile__head--bare)::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: calc(var(--dock-panel-radius) - 1px);
    background-image: url("/svg/logo.svg"), url("/svg/logo.svg");
    background-size: 96px 96px;
    background-position: 0 0, 48px 48px;
    background-repeat: repeat, repeat;
    /* Знак в файле несёт чернила вполсилы, поэтому десятая доля поверх
       даёт те же пять процентов, что были у прежней заливки. */
    opacity: 0.05;
    pointer-events: none;
    /* Переход тот же, что у снимка в зоне (см. .thumb-hoverable):
       зов пустой шапки — то же размытие, только мылит оно рисунок. */
    transition: filter 0.2s ease;
}

/* Пустая шапка зовёт снимок так же, как снимок зовёт удаление: её
   рисунок мылится той же мерой и с тем же переходом. Удаление снимок
   ещё и приближает, но здесь приближать нечего: рисунок лежит по всей
   шапке, и увеличенный край вышел бы за неё. */
.profile__head:not(.profile__head--bare):has(.profile__cover-editor .dd:not(.is-loaded):hover)::before {
    filter: blur(3px) saturate(130%) brightness(1.05);
}

/* Нижняя часть шапки — полоса во всю ширину карточки, залита той же
   светло-серой краской, что и фон самой карточки: у пустой шапки
   полоса и карточка сходятся в одно поле. Сверху у полосы тот же
   радиус, что у самого окна; снизу — на пиксель меньше, на толщину
   рамки: полоса стоит на самом низу окна, и её углы повторяют
   скругление карточки. */
/* ===== Покраска профиля =====

   Владелец красит профиль своим цветом: слайдер авто/HEX лежит
   в правом верхнем углу обложки, поверх снимка, поле цвета
   раскрывается под ним по выбору HEX. Сам цвет приходит атрибутом
   стиля на корне профиля (--color-accent), и всё акцентное внутри
   пересчитывает краску само: рамки окон, зоны снимков, обводки.
   Полосе и пустому кругу достаётся лёгкий подкраш: без покраски
   --profile-accent не задан, и краска вырождается в прежнюю панель. */

.profile__accent {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 2;
    width: 136px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

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

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

.profile__space {
    flex: 0 0 var(--profile-band);
    margin: auto 0 0;
    position: relative;
    border-radius: var(--dock-panel-radius) var(--dock-panel-radius)
                   calc(var(--dock-panel-radius) - 1px)
                   calc(var(--dock-panel-radius) - 1px);
    /* Полоса чуть подкрашивается в цвет профиля: без покраски
       --profile-accent не задан, и краска вырождается в панель. */
    background: color-mix(in srgb,
                          var(--profile-accent, var(--color-panel)) 18%,
                          var(--color-panel));
}

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

   Высота эта — наименьшая: длинное описание с сетапом делает полосу
   выше, и шапка растёт вместе с ней. */
.profile__head--bare {
    flex: 0 0 auto;
    min-height: calc(var(--avatar-size) + var(--avatar-inset) * 2);
}

.profile__head--bare .profile__space {
    flex: 1 1 auto;
    margin: 0;
}

.profile__head--bare .profile__avatar {
    top: var(--avatar-inset);
    bottom: auto;
}

/* Круг под фотографию профиля. Стоит у левого края полосы, чуть
   отступив от него: на 40% диаметра вылезает над полосой, остальное
   лежит в ней. Якорь — по низу полосы: пока её доля вмещает нижнюю
   половину круга, он стоит ровно там же, где стоял при якоре по
   верху. Но на низком окне полоса не вмещает эти 60% диаметра — и
   круг, оставшись на верхнем якоре, свисал бы из-под карточки,
   перекрывая её рамку. Ограничение max не пускает его дальше
   нижнего края: в таком окне круг прижат к дну карточки и вылезает
   над полосой сильнее обычного. */
.profile__avatar {
    position: absolute;
    top: auto;
    bottom: max(0px, calc(var(--profile-band) - var(--avatar-size) * 0.6));
    left: var(--avatar-inset);
    width: var(--avatar-size);
    height: var(--avatar-size);
    padding: var(--avatar-gap);
    border-radius: 50%;
    /* Пустой круг — та же краска, что у полосы, вместе с подкрашем
       цветом профиля: он лежит в ней и не должен выделяться пятном. */
    background: color-mix(in srgb,
                          var(--profile-accent, var(--color-panel)) 18%,
                          var(--color-panel));
    overflow: hidden;
    /* isolate — рамка для блика загрузки (см. ниже): слой блика с
       отрицательным z-index должен остаться внутри контекста круга,
       иначе он уходит под заливку и становится невидимым. */
    isolation: isolate;

    display: grid;
    place-items: center;
}

/* Снимка нет: знак профиля — тот же, что у пункта дока. Краска
   приглушённая, как у города и подсказок: это подложка, а не текст.
   Знак растянут на всю область: у рисунка по краям ещё по единице
   поля (24 против 22 у самого круга), поэтому коробка чуть больше
   области — тогда обводка круга ложится ровно на её край.

   Знак рисует своё содержимое (::before), а сам он — только место
   под ним, и в этом весь смысл: маску браузер накладывает после
   фильтра, поэтому у одного элемента с маской размывать нечего —
   заливка ровная, а форму держит маска и ложится последней. Здесь
   маска живёт слоем ниже, и размытие знака (ниже) видно. */
.profile__avatar-icon {
    position: relative;
    width: calc(100% * 24 / 22);
    height: calc(100% * 24 / 22);
    /* Переход тот же, что у снимка в зоне (см. .thumb-hoverable):
       зов пустого круга — то же размытие, только мылит оно знак. */
    transition: filter 0.2s ease, transform 0.2s ease;
}

.profile__avatar-icon::before {
    content: "";
    position: absolute;
    inset: 0;
    background-color: var(--color-text-muted);
    -webkit-mask: url("/svg/profile.svg") center / contain no-repeat;
    mask: url("/svg/profile.svg") center / contain no-repeat;
}

/* Снимок встаёт на вставку подложки сам: координаты абсолютного
   считаются от полевой коробки круга, куда поле уже входит, —
   поэтому отступ задаём инсетом, а не надеждой на padding предка
   (тот держит только потоковых детей, как знак выше). Проценты
   против авто-дорожки сетки тоже не годятся: дорожка раздувается
   до натуральной ширины снимка.

   Размер нужен явный: снимок — заменяемый элемент, и от инсета
   сам не растягивается (авто-ширина у него натуральная, 512px
   исходника торчали бы из круга на весь профиль). У редакторного
   круга такой беды нет — там снимок держит обёртка модуля. */
.profile__avatar__img {
    position: absolute;
    inset: var(--avatar-gap);
    width: calc(100% - var(--avatar-gap) * 2);
    height: calc(100% - var(--avatar-gap) * 2);
    display: block;
    border-radius: 50%;
    object-fit: cover;
}

/* ===== Блик загрузки =====

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

   Хост — сам круг и сама шапка: оба с обрезкой (у круга была, шапке
   завели радиус и overflow выше), поэтому слой на 30% больше карточки
   с каждой стороны, как у карточек, — запас выхода полосы из кадра.

   Класс is-loading вешает скрипт страницы: снимок назначен, но ещё
   не доехал; load и error его снимают (profile.js). Снимок при
   загрузке прозрачен, сквозь него видно блик; встанет — класс уходит,
   и блик гаснет прежде, чем лечь на готовый кадр.

   z-index ниже всех детей: блик над собственной заливкой хоста, но
   под снимком и знаком. У шапки под бликом окажется и рисунок пустой
   части — он лежит выше слоя, и это верно: рисунок — фактура места,
   блик — ход света поверх неё. */
.profile__avatar.is-loading::after,
.profile__head.is-loading::after,
.profile__avatar:has(.dd__thumb.is-loading)::after,
.profile__head:has(.dd__thumb.is-loading)::after {
    content: "";
    position: absolute;
    z-index: -1;
    inset: -30%;
    background: linear-gradient(
        135deg,
        transparent 40%,
        var(--color-shine) 46%,
        var(--color-shine) 54%,
        transparent 60%
    );
    animation: postcard-shine 1.6s linear infinite;
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    /* Без анимаций блика нет вовсе: загрузка остаётся тихим пустым местом. */
    .profile__avatar.is-loading::after,
    .profile__head.is-loading::after,
    .profile__avatar:has(.dd__thumb.is-loading)::after,
    .profile__head:has(.dd__thumb.is-loading)::after {
        display: none;
    }
}

/* Столбец у правого края полосы: тег, под ним кнопка действия.
   Стоит на верхней линии полосы — на одной высоте с городом.
   Оба прижаты к правому краю — 15px от края полосы. */
.profile__info {
    position: absolute;
    top: 15px;
    right: 15px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
}

/* Тег с ником. На 25% крупнее исходного: шрифт 18px, отступы 8/15.
   По клику текст уходит в буфер обмена, поэтому у тега курсор-рука. */
.profile__tag {
    cursor: pointer;
}

/* Город — вторичный текст: те же 53% чернил поверх полосы, что
   у подсказок зон. Стоит слева, на прежнем месте тега: 15px от верха
   полосы и 265px от левого края (аватарка 30 + 220 + 15 зазор).
   Перед названием — иконка карты. Пока заглушка из разметки, позже
   придёт из базы. */
.profile__city {
    position: absolute;
    top: 15px;
    left: 265px;
    display: flex;
    align-items: center;
    /* Зазор между значком и названием: тот же, что у строк «о себе»
       и сетапа, — тогда их значки и тексты встают вровень. */
    gap: 8px;
    margin: 0;
    color: var(--color-text-muted);
    font-size: 14px;
    /* Высота строки с запасом под нижние выносы букв: у поля она же,
       а поле подрезает всё, что вылезло за его строку. */
    line-height: 1.45;
}

/* Пустой город сервер помечает атрибутом hidden, но display у строки
   сильнее этого атрибута: гасим её сами, иначе остаётся одна иконка. */
.profile__city[hidden] {
    display: none;
}

/* Иконка карты — маской, как в остальных местах сайта: цвет берётся
   из текста, поэтому город и значок всегда одной краски. */
.profile__city-icon {
    flex: none;
    width: 16px;
    height: 16px;
    background-color: currentColor;
    -webkit-mask: url("/svg/map.svg") center / contain no-repeat;
    mask: url("/svg/map.svg") center / contain no-repeat;
}

/* Два текста под городом: о себе, под ним сетап — одна колонка.
   Колонка кончается на середине окна: строка во всю ширину не читается,
   а до правого столбца с тегом и кнопкой ей всё равно далеко.
   min-width — на случай узкого окна: половина минус отступ аватарки
   может выйти совсем тонкой полоской.

   Колонка стоит в потоке полосы, а не поверх неё: тогда полоса сама
   вырастает под длинное описание, и текст не свисает на светлое тело
   окна. Сверху отступ — место города, снизу — тот же, что у полосы. */
.profile__about {
    /* Насколько первая строка описания опущена от верха строки.
       У простого текста она не опущена вовсе, у поля это его рамка
       и верхний отступ. От этого же числа отмеряют своё место значок
       строки и карандаш — иначе они встали бы выше текста. */
    --about-pad: 0px;

    margin: 44px auto 12px 265px;  /* город: 15 сверху + 17 высота + 12 зазор */
    width: calc(50% - 265px);
    min-width: 220px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

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

/* Значок описания встаёт по первой строке текста — как карандаш рядом:
   строк может быть несколько, и по низу он уехал бы. */
.profile__about-text::before,
.profile__about-field::before {
    content: "";
    flex: none;
    width: 16px;
    height: 16px;
    /* Место значка считают от первой строки текста: у поля та же
       строка начинается ниже на рамку и верхний отступ. */
    margin-top: calc(var(--about-pad) + 3px);
    background-color: var(--color-text-muted);
    -webkit-mask: url("/svg/text.svg") center / contain no-repeat;
    mask: url("/svg/text.svg") center / contain no-repeat;
}

/* Значок сетапа — по центру баблов: они выше строки текста. Стоит он
   в списке, а не в ряду: карандаш живёт вне списка и значка не несёт. */
.profile__setup-list::before {
    content: "";
    flex: none;
    align-self: center;
    width: 16px;
    height: 16px;
    background-color: var(--color-text-muted);
    -webkit-mask: url("/svg/devices.svg") center / contain no-repeat;
    mask: url("/svg/devices.svg") center / contain no-repeat;
}

/* Сам текст — почти чернила: на тёмной полосе читается, но не кричит.
   Переводы строк из окна сохраняются: описание набирают абзацами.
   Длинное слово без пробелов иначе уезжает за край окна — переносим
   его, как только оно перестаёт помещаться в строку. */
.profile__about-text {
    /* Строка со значком: значок отдельным звеном, текст — своим. */
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 0;
    color: var(--color-text-secondary);
    font-size: 14px;
    /* Высота строки с запасом под нижние выносы букв — та же, что
       у поля правки: иначе владелец и остальные читались бы по-разному. */
    line-height: 1.45;
    white-space: pre-line;
    overflow-wrap: break-word;
}

/* Сетап — список баблов и карандаш за ним. Список переносится, если
   не влезает в ширину области, а карандаш стоит вне его и остаётся
   на месте справа. По верху — чтобы карандаш был вровень с первой
   строкой списка, а не уезжал на середину длинного сетапа. */
.profile__setup {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.profile__setup-list {
    /* Список забирает всё, что осталось от карандаша: длинный сетап
       ложится строками, а не уезжает за край колонки.
       align-items: flex-start — чтобы первая строка баблов была вровень
       с карандашем (он выравнивается по верху контейнера через
       .profile__setup { align-items: flex-start }). */
    flex: 0 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 8px;
}

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

   Высота — как у бабла: пустой ряд не ниже заполненного, и значок
   с подсказкой не уезжают вверх относительно строки баблов. */
.profile__setup-hint {
    display: flex;
    align-items: center;
    min-height: var(--bubble-height, 30px);
    color: var(--color-text-muted);
    font-size: 12px;
}

.profile__setup-list:has(.bubble) .profile__setup-hint {
    display: none;
}

/* Кружок плюса — в ряд с баблами: та же высота, что у плашки, и плюс
   в ней чуть меньше. Виден только в правке: в покое предметы
   не добавляют. */
.profile__setup [data-pb-action="setup-add"] {
    --pb-size: 30px;
    --pb-plus: 12px;
}

.profile__setup:not(.is-editing) [data-pb-action="setup-add"] {
    display: none;
}

/* Карандаш ряда — тот же, что у строки описания: правится весь сетап,
   а не одно поле, поэтому кнопка стоит в ряду по высоте бабла. */
.profile__setup-edit {
}

/* Иконка карандаша — маской, как у кнопки строки ввода: цвет берётся
   из текста кнопки. */
/* Незаполненные части профиля не занимают места: ни строки, ни зазора. */
.profile__about-text:empty,
.profile__setup:empty {
    display: none;
}

.profile__tag .tag {
    font-size: 18px;
    padding: 8px 15px;
    border-radius: 15px;
    transition: background-color var(--motion-control-duration, 220ms) var(--motion-ease, ease);
}

/* Скопировали — метка вспыхивает на мгновение: подтверждение,
   что текст уже в буфере. */
.profile__tag.is-copied .tag {
    background: var(--color-selected);
}

.profile__tag:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 4px;
    border-radius: 15px;
}

/* Сетка постов — мозайка: пока скрипта нет, серверная раскладка
   держится колонками (пять на широком экране, три на среднем, одна
   на телефоне). Как только mosaic.js измерил ширину сетки, карточки
   перекладываются рядами во всю ширину (ряды — .mosaic__row в
   main.css): карточка несёт свою высоту (aspect-ratio по --ratio
   из postcard.css), и фото лежит целиком, без обрезки. Калибр ячейки
   общий с мозаикой ленты (--post-size задаёт ширину колонки в
   main.css), карточки на обеих страницах одного размера и одной
   формы. От карточки профиля сетку отделяет зазор мозаики
   (--page-gap). */

/* ===== Правка на самой странице =====

   Части шапки владельцу приходят редакторами: снимки — зонами
   draganddrop с правкой кадра, город и «о себе» — полями, которые
   включает карандаш. Разметку выбирает сервер (app.py), поэтому
   в покое страница выглядит одинаково у всех, а правка есть только
   у владельца.

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

/* --- Фото шапки ---

   Зона лежит во всю шапку: снимок и есть её фон, а полоса и круг
   рисуются поверх него. */
.profile__cover-editor {
    position: absolute;
    inset: 0;
}

.profile__cover-editor .picture-editor {
    position: absolute;
    inset: 0;
}

/* Зона занимает всю шапку: снимок и есть её фон, а полоса и круг
   рисуются поверх него. Подложку, обводку, размытие на наведении
   и корзину зона берёт у модуля — на странице они такие же, как
   в окне. */
.profile__cover-editor .dd {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    padding: 0;
    /* Радиус на всех четырёх углах: низ шапки теперь совпадает
       с низом карточки, и квадратные углы зоны перекрыли бы
       красную рамку окна. Раньше низ прятался под телом окна,
       и радиус был нужен только сверху. */
    border-radius: calc(var(--dock-panel-radius) - 1px);
}

/* Подсказки у зоны нет: пустая шапка молчит, а о снимке говорит
   значок загрузки на наведении (см. draganddrop). */

/* Кадр правки совпадает с зоной — то есть со всей шапкой. Так в правке
   видно ровно то, что уйдёт в файл: снимок целиком, без замыленных
   боков. Готовый размер при этом повторяет форму шапки, поэтому после
   сохранения снимок встаёт на то же место, без приближения. */

/* Плашка правки встаёт не под кадром, а под светлой частью. Кадр
   у шапки — вся шапка, и под кадром плашка оказалась бы под всей
   тёмной полосой с городом и «о себе», далеко от снимка. Место
   считаем по тем же меркам слоя, что и модуль: он передаёт края кадра
   в --pe-y и --pe-b. Отступ тот же, что у плашки в модуле, — 14px;
   а если места внизу нет, модуль сам уводит плашку вверх (is-above),
   и тогда это правило молчит. */
.picture-editor__crop:not(.is-above)[data-pe-crop="profile-cover"] .picture-editor__plate {
    top: calc(var(--pe-y) + (var(--pe-b) - var(--pe-y)) * (1 - var(--profile-band-share)) + 14px);
}

/* Корзина на снимке шапки встаёт по центру светлой части: центр всей
   шапки приходится уже на полосу, и значок уезжает вниз. */
.profile__cover-editor .thumb-remove {
    bottom: var(--profile-band);
}

/* Значки — по центру светлой части, а не всей шапки: место им сужает
   сама полоса, и внутри оставшегося они встают по центру. Это значок
   загрузки у пустой зоны и значок правки у занятой: оба зовут к
   светлой части, оба по наведению. */
.profile__cover-editor .dd[data-dd-upload="1"]:not(.is-loaded):hover::after,
.profile__cover-editor .dd[data-dd-edit="1"].is-loaded:hover::after {
    bottom: var(--profile-band);
}

/* Правка снимка ничего на странице не гасит: и полоса с городом,
   тегом и описанием, и круг остаются как есть — правят снимок,
   а не подписи. */

/* --- Круг аватарки ---

   Круг — та же зона: снимок обрезается его формой. Всё остальное зона
   берёт у модуля, поэтому с наведением, размытием и корзиной круг
   выглядит как в окне. */
/* Зона правки стоит на той же вставке, что и готовый снимок:
   inset: 0 лёг бы поверх поля подложки и съел бы кайму круга. */
.profile__avatar--editable .picture-editor {
    position: absolute;
    inset: var(--avatar-gap);
}

.profile__avatar--editable .dd {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    padding: 0;
}

/* Пустой круг показывает знак профиля — подсказке зоны места здесь нет.
   Знак лежит поверх подложки зоны: подложка непрозрачная, и знак под
   ней был бы не виден. */
.profile__avatar--editable .dd__hint {
    display: none;
}

.profile__avatar--editable .profile__avatar-icon {
    position: relative;
    z-index: 1;
    /* Знак лежит поверх зоны, но клик — её: иначе пустой круг нельзя
       выбрать, и правка не открывается. */
    pointer-events: none;
    /* Знак значит ровно одно — «снимка нет». Грузится ли кадр или
       меняется на другой — знак молчит: вспыхнуть над снимком или
       дырой вместо него он не должен ни на кадр. Поэтому по умолчанию
       знака нет вовсе, а пустоту показывает отметка render'а. */
    display: none;
}

/* Пустой круг показывает знак. Пустым место делает отрисовка зоны
   (is-empty, см. dd.js), и до неё знак не встаёт: иначе он мигал бы
   при каждом открытии страницы, пока зона ещё не отрисована.

   Кадр с разметки (is-booting) тоже показывает знак: снимок назначен,
   но ещё не дорисовался — знак и блик стоят вместе, пока кадр не
   встанет на экран (см. settle в dd.js). */
.profile__avatar--editable:has(.dd.is-empty) .profile__avatar-icon,
.profile__avatar--editable:has(.dd.is-booting) .profile__avatar-icon {
    display: block;
}

/* Пустой круг зовёт снимок так же, как снимок зовёт удаление: знак
   мылится той же мерой и с тем же переходом, а поверх встаёт значок
   загрузки — как корзина поверх замыленного снимка. Знак при этом
   не гаснет: мылим именно его, и гасить было бы нечего. */
.profile__avatar--editable:has(.dd.is-empty:hover) .profile__avatar-icon {
    filter: blur(3px) saturate(130%) brightness(1.05);
    transform: scale(1.04);
}

/* --- Город ---

   Поле с карандашом стоит на месте строки города: те же 15 сверху,
   265 слева и та же иконка карты перед названием. Выключенное поле
   не занимает и пикселя лишнего — без заливки, обводки и отступов, —
   поэтому строка стоит там же, где у остальных. */
.profile__city-field {
    position: absolute;
    top: 10px;
    left: 265px;
    display: flex;
    align-items: center;
    /* Тот же зазор, что у строки города у остальных. */
    gap: 8px;
}

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

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

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

.profile__city-field::before {
    content: "";
    flex: none;
    width: 16px;
    height: 16px;
    background-color: var(--color-text-muted);
    -webkit-mask: url("/svg/map.svg") center / contain no-repeat;
    mask: url("/svg/map.svg") center / contain no-repeat;
}

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

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

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

/* --- О себе ---

   Поле с карандашом стоит на месте строки описания: тот же шрифт,
   цвет и высота. Выключенное поле от строки неотличимо, а высоту
   берёт по содержимому — описание набирают абзацами, и в покое оно
   должно быть видно целиком, как обычный текст. */
.profile__about-field {
    /* Первая строка поля опущена на его рамку. */
    --about-pad: 1px;

    display: flex;
    align-items: flex-start;
    gap: 8px;
}

/* Поле открыли — к рамке добавился верхний отступ, и значок с карандашом
   едут вместе с текстом. */
.profile__about-field:has(.text:enabled) {
    --about-pad: 7px;
}

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

/* Как и у города: отступы только справа и снизу — они дают буквам
   воздух, а текст остаётся на месте строки. Слева и сверху отступов
   нет: там они сдвинули бы текст с места. */

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

.profile__about-edit {
    /* Карандаш строки тот же, что у города: описание набирают строкой,
       и кнопка в 44 пикселя была бы выше самого описания. Отрицательные
       поля возвращают строке её высоту — сетап не съезжает вниз,
       а левое отводит кнопку от последней буквы описания. */
    flex: none;
    /* Верхнее поле ведёт кнопку за первой строкой текста — вместе
       со значком строки. Нижнее отрицательное: кнопка выше строки,
       и без него сетап съехал бы вниз. */
    margin: calc(var(--about-pad) - 4px) 0 -6px 4px;
}

/* Иконка карандаша — маской, как у кнопки строки ввода: цвет берётся
   из текста кнопки. */
@media (prefers-reduced-motion: reduce) {
    .profile__action,
    .profile__tag .tag {
        transition: none;
    }
}


/* Strict display budgets: no inner scroll and no growing header. */
.profile__window { min-height: 280px; max-height: 440px; overflow: hidden; }
.profile__window:has(.profile__head--bare) { height: clamp(280px, 40vh, 440px); }
.profile__head, .profile__head--bare { min-height: 0; flex: 1 1 0; }
.profile__space { position: absolute; inset: auto 0 0; height: var(--profile-band); min-height: 150px; overflow: hidden; }
.profile__about { position: absolute; left: 265px; right: 50%; top: 44px; bottom: 12px; margin: 0; width: auto; min-width: 0; min-height: 0; overflow: hidden; gap: 6px; }
.profile__about-field { position: relative; display: inline-grid; grid-template-columns: minmax(0,max-content) 30px; gap: 0 6px; align-items: start; width: fit-content; max-width: 100%; min-width: 0; padding-left: 24px; flex: none; }
.profile__about-field::before, .profile__about-text::before { position: absolute; left: 0; top: 3px; margin: 0; }
.profile__about-rendered { grid-column: 1; grid-row: 1; font-size: 14px; line-height: 1.45; max-height: 2.9em; overflow: hidden; }
.profile__about-rendered p { margin: 0; }
.profile__about-field [data-text="about"] { grid-column: 1; grid-row: 1; width: 100%; min-width: 0; height: 3.4em; max-height: 3.4em; overflow: hidden; field-sizing: fixed; }
.profile__about-field [data-text="about"]:disabled { display: none; }
.profile__about-field:has([data-text="about"]:enabled) { width: 100%; grid-template-columns: minmax(0,1fr) 30px; }
.profile__about-field:has([data-text="about"]:enabled) [data-about-rendered] { display: none; }
.profile__about-edit { grid-column: 2; grid-row: 1; margin: -3px 0 0; }
.profile__about-text { position: relative; display: block; padding-left: 24px; max-height: 2.9em; overflow: hidden; flex: none; }
.profile__markdown-help { display: none; grid-column: 1 / -1; font-size: 10px; line-height: 1.25; color: var(--color-text-muted); }

.profile__setup { min-width: 0; max-width: 100%; flex: none; }



.profile__setup .plusbutton[hidden] { display: none; }
@media (max-width: 800px) { .profile__about { left: min(265px, 35%); right: 50%; } }

.profile__setup .plusbutton { --pb-size: 30px; --pb-plus: 14px; }
.profile__about:has([data-text="about"]:enabled) { top: 38px; bottom: 6px; gap: 3px; }
.profile__about-field [data-text="about"] { height: 3em; max-height: 3em; padding: 2px 6px; }

.profile__about-text p { margin: 0; }

/* Two complete text lines and two tag rows, including focus-ring clearance. */
.profile__window, .profile__window:has(.profile__head--bare) { height: clamp(340px, 42vh, 480px); min-height: 340px; max-height: 480px; }
.profile__head { --profile-band: max(204px, calc(var(--profile-band-share) * 100%)); }
.profile__space { min-height: 204px; }
.profile__about, .profile__about:has([data-text="about"]:enabled) { top: 44px; bottom: 12px; gap: 8px; }
.profile__about-field [data-text="about"] { box-sizing: border-box; line-height: 1.45; padding: 2px 6px; height: calc(2.9em + 6px); max-height: calc(2.9em + 6px); }
.profile__about-rendered, .profile__about-text { max-height: calc(2.9em + 2px); }
.profile__markdown-help, .profile__markdown-help[hidden] { display: none; }


.profile__setup .bubble__edit { min-width: 0; max-width: 100%; overflow: hidden; }
.profile__setup .bubble__field { min-width: 0; max-width: 100%; }


/* Compact natural-width pills, with wrapping at the available edge. */
.profile__setup-list {
    flex: 0 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-content: flex-start;
    align-items: flex-start;
    gap: 8px;
    max-height: none;
    overflow: visible;
    padding: 4px;
    margin: -4px 0;
}
.profile__setup-list > .bubble {
    flex: 0 0 auto;
    width: max-content;
    min-width: 0;
    max-width: 100%;
    white-space: normal;
    padding-top: 4px;
    padding-bottom: 4px;
}
.profile__setup [data-bubble-text] {
    min-width: 0;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
    overflow-wrap: anywhere;
}
.profile__setup-list > .plusbutton { justify-self: start; }
.profile__window, .profile__window:has(.profile__head--bare) {
    height: clamp(380px, 44vh, 520px);
    min-height: 380px;
    max-height: 520px;
}
.profile__head { --profile-band: max(240px, calc(var(--profile-band-share) * 100%)); }
.profile__space { min-height: 240px; }

/* Give glyphs and the edit button their own unclipped vertical space. */
.profile__about-field { padding-top: 4px; }
.profile__about-field::before { top: 7px; }
.profile__about-edit { margin-top: 0; }
.profile__about-rendered, .profile__about-text { padding-top: 3px; padding-bottom: 3px; max-height: calc(2.9em + 6px); }
.profile__about-field [data-text="about"] { padding: 4px 6px; height: calc(2.9em + 10px); max-height: calc(2.9em + 10px); }

/* Reserve the measured bounded setup instead of clipping its last row. */
.profile__window, .profile__window:has(.profile__head--bare) {
    height: max(clamp(380px, 44vh, 520px), calc(var(--profile-info-height, 240px) + 140px));
    min-height: calc(var(--profile-info-height, 240px) + 140px);
    max-height: none;
}
.profile__head { --profile-band: max(var(--profile-info-height, 240px), calc(var(--profile-band-share) * 100%)); }
.profile__space { min-height: var(--profile-info-height, 240px); }

/* The icon and pencil do not consume artificial space between pills. */
.profile__setup { position: relative; }
.profile__setup-list { position: relative; --compact-leading: 24px; --compact-trailing: 38px; padding-left: 28px; padding-right: 42px; }
.profile__setup-list::before { position: absolute; left: 4px; top: 7px; margin: 0; }
.profile__setup-edit { position: absolute; top: 0; left: min(var(--setup-edit-left, 28px), calc(100% - 30px)); margin: 0; }


/* The editor and the saved Markdown have the same two-line content box. */
.profile__about-field { width: 100%; grid-template-columns: minmax(0, 1fr) 30px; }
.profile__about-rendered { box-sizing: border-box; width: 100%; padding: 4px 7px; max-height: calc(2.9em + 8px); }
.profile__about-field [data-text="about"] { white-space: pre-wrap; overflow-wrap: break-word; }

/* Shared icon and content columns, including the inputs' inner padding. */
.profile__head {
    --profile-info-icon: 16px;
    --profile-info-gap: 8px;
    --profile-info-content: calc(var(--profile-info-icon) + var(--profile-info-gap));
    --profile-info-input-inset: 7px;
}
.profile__city-field {
    padding-left: calc(var(--profile-info-content) - var(--profile-info-input-inset));
    gap: 0;
}
.profile__city-field::before {
    position: absolute;
    left: 0;
    width: var(--profile-info-icon);
}
.profile__city-field .inputfield[data-variant="inline"] .inputfield__input,
.profile__city-field .inputfield[data-variant="inline"] .inputfield__input:disabled,
.profile__city-field .inputfield[data-variant="inline"] .inputfield__hint {
    padding: 4px 6px;
}
.profile__city { gap: var(--profile-info-gap); }
.profile__city-icon { width: var(--profile-info-icon); }
.profile__about-field {
    padding-left: calc(var(--profile-info-content) - var(--profile-info-input-inset));
}
.profile__about-field::before, .profile__about-text::before {
    left: 0;
    width: var(--profile-info-icon);
}
.profile__about-text { padding-left: var(--profile-info-content); }
.profile__setup-list {
    --compact-leading: calc(var(--profile-info-content) - 4px);
    padding-left: var(--profile-info-content);
}
.profile__setup-list::before { left: 0; width: var(--profile-info-icon); }

/* Align the first text line with its icon; avoid stacked vertical padding. */
.profile__about-field { padding-top: 0; }
.profile__about-field::before { top: 7px; }
.profile__about-edit { margin-top: 0; }

@media (hover: hover) and (pointer: fine) {
    .profile__city-field .inputfield__edit,
    .profile__about-edit,
    .profile__setup-edit {
        opacity: 0;
        pointer-events: none;
        transition: opacity var(--motion-control-duration, 220ms) var(--motion-ease, ease),
                    background-color var(--motion-control-duration, 220ms) var(--motion-ease, ease),
                    color var(--motion-control-duration, 220ms) var(--motion-ease, ease);
    }
    .profile__city-field:hover .inputfield__edit,
    .profile__city-field:focus-within .inputfield__edit,
    .profile__about-field:hover .profile__about-edit,
    .profile__about-field:focus-within .profile__about-edit,
    .profile__about-field:has(.text:enabled) .profile__about-edit,
    .profile__setup:hover .profile__setup-edit,
    .profile__setup:focus-within .profile__setup-edit,
    .profile__setup.is-editing .profile__setup-edit {
        opacity: 1;
        pointer-events: auto;
    }
}
@media (prefers-reduced-motion: reduce) {
    .profile__city-field .inputfield__edit, .profile__about-edit, .profile__setup-edit { transition: none; }
}

.profile__setup-list::before { top: var(--setup-icon-top, 11px); }

/* A visitor's profile without a cover only reserves space for its content. */
.profile__window:has(.profile__head--bare) {
    height: auto;
    min-height: 0;
    max-height: none;
}

.profile__head.profile__head--bare {
    flex: 0 0 auto;
    min-height: calc(var(--avatar-size) + var(--avatar-inset) * 2);
}

/* Keep the pencil beside the displayed description, not at the row edge. */
.profile__about-field { width:fit-content; max-width:100%; grid-template-columns:minmax(0,max-content) 30px; }
.profile__about-field [data-text="about"]:disabled { display:none; }
.profile__about-field:has([data-text="about"]:enabled) { width:100%; grid-template-columns:minmax(0,1fr) 30px; }

.profile__about-field:has([data-text="about"]:placeholder-shown) .profile__about-rendered {
    color:var(--color-text-muted);
}
.profile__about-field:has([data-text="about"]:enabled) {
    width:fit-content;
    max-width:100%;
    grid-template-columns:minmax(0,max-content) 30px;
}
.profile__about-field [data-text="about"]:enabled {
    width:auto;
    min-width:18ch;
    max-width:100%;
    field-sizing:content;
}

.profile__about-field [data-text="about"]:enabled {
    height:auto;
    min-height:0;
    max-height:calc(2.9em + 10px);
    field-sizing:content;
}

.profile__about-field > .tooltip { display:none; grid-column:1; grid-row:1; justify-self:end; align-self:start; margin:6px 5px 0 0; z-index:2; }
.profile__about-field:has([data-text="about"]:enabled) > .tooltip { display:inline-flex; }
.profile__about-field [data-text="about"]:enabled { padding-right:30px; }
