/* =============================================================================
   cabinet.css — кабинет заточника TIXOGEL
   Тот же язык, что на сайте: чёрный холст, дисплейный Unbounded,
   hairline-линии вместо панелей, пилюли, моноширинные цифры.
   ============================================================================= */

:root{
  /* Тёмная тема — основная. Светлая задаётся ниже переопределением
     этих же переменных, поэтому весь остальной файл о теме не знает
     и менять его не пришлось.

     ХОЛСТ БОЛЬШЕ НЕ ЧИСТО ЧЁРНЫЙ. #000 казался честным решением, но у него
     нет уровня ниже: под ним нельзя ничего затемнить, и любая тёмная
     фотография упирается в холст без границы. В ленте это видно сразу —
     два макроснимка подряд читаются как один бесконечный кадр, потому что
     стык фото с фоном физически отсутствует. #0a0b0c отличается от чёрного
     на глаз почти неразличимо, но даёт снимку край, а интерфейсу — запас
     на четыре поверхности вместо двух. */
  --void:#0a0b0c;    /* холст приложения                                   */
  --paper:#131518;   /* приподнятая поверхность: списки, вывод диагностики */
  --bone:#1b1e22;    /* место под ещё не загруженное фото, вложенные блоки */
  --raised:#262a30;  /* активный чип, подложка вкладки, поле ввода         */

  /* Серые уведены в холод. Прежние #c9c9c9 и #4c4c4c — чистые нейтрали, и
     рядом с ними сталь на снимках казалась чуть тёплой: глаз сравнивает
     цвет кромки с ближайшим серым, а не с абсолютом. Синеватая примесь в
     интерфейсе снимает это сравнение — металл остаётся таким, каким его
     сняли. Тот же довод, по которому светлая тема ушла с оливковой бумаги
     на нейтральную. */
  --frost:#f2f4f6;      /* заголовки, значения, цепочка камней   17.9:1 */
  --ash:#c2c9d0;        /* основной текст абзацев                11.8:1 */
  --mist:#8b939c;       /* подписи, характеристики                6.3:1 */
  --graphite:#656d76;   /* неактивное: время, счётчики, прочерк   3.8:1 */
  /* Прежний #4c4c4c давал 2.45:1 — ниже порога даже для крупного текста,
     а им набраны время публикации и счётчики просмотров. 3.75:1 —
     минимум, при котором мелкая подпись остаётся подписью, а не пятном. */

  --hairline:rgba(255,255,255,.10);
  --hairline-dim:rgba(255,255,255,.055);
  /* Обводка ПОВЕРХ содержимого, а не под ним: волосяная линия идёт внутрь
     фотографии, поэтому она светлее холста, а не темнее. Без неё тёмный
     снимок вытекает в фон и карточка теряет край — ровно то, из-за чего
     лента выглядела сплошным полотном. */
  --sheen:rgba(255,255,255,.08);
  /* Затемнение под метаданными поверх снимка */
  --scrim:linear-gradient(to top,rgba(10,11,12,.92),rgba(10,11,12,0));

  /* АКЦЕНТ ЧЁРНОЙ ТЕМЫ — кислотный жёлтый. Единственный цвет в чёрном
     кабинете, поэтому он и работает как знак отличия: его больше нигде нет.
     Ровный, без градиентов: перелив притягивал взгляд к наградам сильнее,
     чем к самим заточкам.

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

     Проверка это подтверждает численно: #d8ff2b даёт 17,1:1 на чёрном
     и 1,07:1 на кремовой бумаге. Один hex тут невозможен физически. */
  --acid:#d8ff2b;
  --acid-dim:rgba(216,255,43,.16);
  /* Обводка акцентом — для контуров награды и рамки активного поля.
     Сплошной акцент в рамке спорит с заливкой значка рядом. */
  --acid-line:rgba(216,255,43,.34);
  /* Ещё тише, чем --acid-dim: подложка целой плитки, а не значка в 11 px.
     На площади в сто пикселей та же прозрачность даёт грязный оливковый —
     кислотный жёлтый на тёмном мутнеет быстрее любого другого тона. */
  --acid-wash:rgba(216,255,43,.08);
  /* Текст на акцентной заливке: цвет взят из самого акцента и уведён
     в темноту — так надпись остаётся частью значка, а не наклейкой на нём */
  --on-acid:#1b2400;
  /* В чёрной теме акцентный текст и акцентная заливка — один цвет: на
     холсте он даёт 17:1 и читается в любом кегле. Разводить их приходится
     только в светлой (см. --acid-text ниже). */
  --acid-text:var(--acid);

  /* Состояния живут отдельно от бренда. Пока «получилось» и «ошибка»
     красились акцентом и danger вперемешку, цвет в интерфейсе значил
     «здесь что-то важное» вообще — а должен значить, ЧТО именно. */
  --danger:#ff6f61;
  --success:#5cd08a;
  --warning:#e9b444;

  /* Полупрозрачные подложки: у стекла и накладных панелей свой оттенок */
  --glass:rgba(18,20,23,.72);
  --glass-top:rgba(10,11,12,.55);
  --overlay:rgba(10,11,12,.97);
  --raise:rgba(255,255,255,.08);
  --f-display:'Unbounded',sans-serif;
  --f-body:'Manrope',sans-serif;
  --f-mono:'JetBrains Mono',monospace;
  --pad-x:clamp(18px,5vw,72px);
  --nav-h:60px;
}

/* ── Светлая тема ────────────────────────────────────────────────────────────
   БУМАГА, ЧЕРНИЛЬНЫЙ ТЕКСТ, ОДИН ОРАНЖЕВЫЙ.

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

   Холст доведён до нейтрали. Прежний #fafaf8 был на два пункта теплее по
   синему — остаток кремовой бумаги, и та же логика, по которой отсюда убрали
   оливковый, требует убрать и его: любой тёплый уклон подкрашивает сталь
   на снимках. #f6f7f8 не тёплый и не холодный, снимок на нём стоит ровно. -- */
[data-theme="light"]{
  --void:#f6f7f8;            /* холст                                  */
  --paper:#ffffff;           /* приподнятые поверхности                */
  --bone:#edeff1;            /* подложка под фото, вложенные блоки     */
  --raised:#e4e7ea;          /* активный чип, поле ввода               */
  --frost:#101214;           /* чернила: заголовки и заливки   17.5:1  */
  --ash:#414851;             /* основной текст абзацев          8.6:1  */
  --mist:#656d7a;            /* приглушённый текст              4.9:1  */
  --graphite:#7d848f;        /* «неактивное»: время, счётчики   3.5:1  */
  /* Прозрачные нарочно: волосяные линии идут и поверх фотографий, а сплошной
     цвет там читался бы как наклеенная полоска. */
  --hairline:rgba(16,18,20,.13);
  --hairline-dim:rgba(16,18,20,.07);
  /* На бумаге снимок отделяется тенью, а не свечением: линия уходит
     в тёмное, иначе светлая обводка на светлом холсте не видна вовсе. */
  --sheen:rgba(16,18,20,.10);
  --scrim:linear-gradient(to top,rgba(16,18,20,.85),rgba(16,18,20,0));

  /* Акцент светлой темы. На бумаге даёт 2.85:1 — для иконок, заливок, полос
     и обводок этого достаточно, там контраст не про чтение. */
  --acid:#ff5e24;
  --acid-dim:rgba(255,94,36,.22);
  --acid-line:rgba(255,94,36,.32);
  --acid-wash:rgba(255,94,36,.13);
  /* Белый на #ff5e24 даёт 3.05:1 и на значке «★ 42» в 11 px не читается;
     тёмно-коричневый — 5.87:1. Тот же приём, что у кислотного в чёрной теме:
     цвет надписи взят из самого акцента и уведён в темноту. */
  --on-acid:#2b0f00;
  /* Тот самый «тёмный сородич», который здесь был обещан на случай, если
     мелкий акцентный ТЕКСТ окажется неразборчивым. Мест ровно четыре:
     титул уровня под именем, подпись награды, ссылка в строке скидок
     и промокод. Заливкам он не нужен — только буквам. 4.9:1. */
  --acid-text:#c1400a;

  --danger:#c22d1e;
  --success:#0e7a47;
  --warning:#8a5a00;

  --glass:rgba(246,247,248,.72);
  --glass-top:rgba(246,247,248,.72);
  --overlay:rgba(246,247,248,.97);
  --raise:rgba(16,18,20,.06);
  /* Системная иконка календаря рисуется чёрной: на тёмной подложке её
     приходится инвертировать, на бумаге — оставить как есть. */
  --pick-invert:0;
}

*,*::before,*::after{box-sizing:border-box}
/* Документ прибит намертво. На iOS этого мало добиться одним overflow:hidden:
   страница всё равно «оттягивается» пальцем и при двойном тапе, и вместе с ней
   уезжает всё, что стоит на position:fixed, — на записи экрана панель вкладок
   так и сползала вниз на 60 pt.

   Лечится тремя вещами сразу:
     · height:100% вместо любых vh-единиц — они на iOS врут в установленном
       приложении;
     · position:fixed на самом body — документу физически некуда двигаться;
     · touch-action:manipulation — отключает двойной тап для масштабирования,
       который и дёргал раскладку (user-scalable=no iOS игнорирует).

   Прокрутка живёт только внутри .app-scroll — там она и нужна. */
html{
  margin:0;padding:0;background:var(--void);
  /* min-height, а не height: документ должен дорисовываться до физического
     низа экрана, каким бы этот низ ни оказался. dvh знает про панели
     и «остров», -webkit-fill-available выручает там, где dvh ещё не понимают. */
  min-height:100dvh;min-height:-webkit-fill-available;
  -webkit-text-size-adjust:100%;
  /* Двойной тап больше не масштабирует, но обычная прокрутка остаётся */
  touch-action:manipulation;
}
body{
  margin:0;padding:0;width:100%;background:var(--void);
  /* +1px намеренно: на iOS положение fixed-элементов зависит от того,
     прокручивается страница или нет. Пустой экран не прокручивался, и панель
     вкладок вставала на 108 pt выше, чем на экране с лентой. Лишний пиксель
     делает поведение одинаковым везде. */
  min-height:calc(100dvh + 1px);
  min-height:calc(-webkit-fill-available + 1px);
  touch-action:manipulation;
  /* Родной отскок выключен. Раньше он был единственным движением при
     оттягивании, и его берегли. Теперь ленту сдвигает вниз сам кабинет
     (см. «обновление потягиванием» ниже), и родной отскок складывался бы
     с этим сдвигом: палец на 60 px, лента на 120. Одно движение —
     один источник.

     Если на старой iOS отскок всё-таки останется (свойство понимают не все
     версии), лента будет уезжать дальше положенного — лечится возвратом
     на auto и отказом от своего сдвига в пользу одного лишь отскока. */
  overscroll-behavior-y:contain;
}

/* Приложение — обычный поток документа.
   Раньше здесь был заблокированный документ и своя прокрутка внутри: из-за
   этого низ экрана в установленном приложении оставался незакрашенным —
   контейнер был короче физического экрана и не дотягивался. Теперь высоту
   определяет сам документ, и такой щели взяться неоткуда. */
#app{
  min-height:100dvh;min-height:-webkit-fill-available;
  display:flex;flex-direction:column;
}
#app[hidden]{display:none}
.app-scroll{
  flex:1 1 auto;
  /* Вбок кабинет не прокручивается никогда: горизонтальная прокрутка живёт
     только внутри своих полос (снимки в карточке, плитки наград). Если
     какой-то блок однажды вылезет за край, пусть лучше обрежется, чем
     утащит за собой весь экран. */
  overflow-x:hidden;
  /* Место под накрывающую шапку: её высоту меряет app.js */
  padding-top:var(--topbar-h, 64px);
  /* Место под накладную панель: её высота плюс зона домашнего индикатора.
     Без этого последняя карточка ленты пряталась бы под панелью. */
  padding-bottom:calc(var(--tabbar-h, 64px) + 18px + env(safe-area-inset-bottom, 0px));
}
body{
  font-family:var(--f-body);color:var(--ash);
  font-size:15px;line-height:1.5;
  -webkit-font-smoothing:antialiased;
}
::selection{background:var(--frost);color:var(--void)}
a{color:var(--frost);text-decoration:none}
button{font-family:inherit}
input,select,textarea{font-family:inherit;font-size:16px} /* 16px = iOS не зумит */

/* ── типографика ──────────────────────────────────────────────────────────── */
.eyebrow{
  font:400 11px/1 var(--f-body);letter-spacing:.22em;text-transform:uppercase;
  color:var(--mist);margin:0 0 14px;
}
/* Верхняя граница снижена с 52 до 34 px. На телефоне слово «НОЖИ» в 52 px
   занимает четверть первого экрана, а под ним стоят три строки списка —
   заголовок весит больше, чем всё, что он называет. Разделу нужна вывеска,
   а не афиша: 28-34 px хватает, чтобы отделить раздел от содержимого,
   и остаётся место самому содержимому.

   На широком экране прежний кегль был уместен, поэтому нижняя граница
   не тронута — сжалась только верхняя. */
h1.display{
  font:700 clamp(26px,6vw,34px)/1.02 var(--f-display);
  letter-spacing:-.02em;text-transform:uppercase;color:var(--frost);margin:0 0 18px;
}
h2.section{
  font:700 clamp(17px,4vw,22px)/1.15 var(--f-display);
  text-transform:uppercase;color:var(--frost);margin:0 0 16px;
}
.muted{color:var(--mist)}
.mono{font-family:var(--f-mono)}

/* ── каркас ───────────────────────────────────────────────────────────────── */
.wrap{padding:26px var(--pad-x) 30px;max-width:900px;margin:0 auto}
.view{display:none}
.view.active{display:block;animation:fade .2s ease}
@keyframes fade{from{opacity:0}to{opacity:1}}

/* ── шапка ────────────────────────────────────────────────────────────────── */
/* Шапка накрывает системную зону размытым стеклом: содержимое проезжает
   под ней, а часы и значки связи остаются читаемыми на любом снимке.
   Сплошная чёрная полоса поверх фотографии выглядела бы обрезкой кадра. */
.topbar{
  position:fixed;top:0;left:0;right:0;z-index:50;
  display:flex;align-items:center;justify-content:space-between;
  /* В режиме приложения контент уходит под статус-бар, поэтому добавляем
     его высоту сверху. На обычной вкладке отступ нулевой. */
  padding:calc(14px + env(safe-area-inset-top,0px)) var(--pad-x) 14px;
  background:var(--glass-top);
  -webkit-backdrop-filter:blur(18px) saturate(130%);
          backdrop-filter:blur(18px) saturate(130%);
  border-bottom:1px solid var(--hairline-dim);
  /* Свой слой композитора: иначе размытие пересчитывается на каждый кадр
     прокрутки и полоса моргает */
  transform:translateZ(0);
}
.topbar .brand{font:700 15px/1 var(--f-display);letter-spacing:.02em;color:var(--frost)}
.topbar .who{font:400 12px/1 var(--f-mono);color:var(--mist)}

/* ── нижняя навигация ─────────────────────────────────────────────────────── */
/* =============================================================================
   ПАНЕЛЬ ВКЛАДОК — плавающая капсула на матовом стекле

   Форма взята у ленты Instagram, и взята не как украшение. Панель с подписями
   занимала полосу в 64 px и читалась как отдельный этаж интерфейса: сначала
   лента, потом граница, потом меню. Капсула без подписей — 56 px, и она не
   этаж, а предмет, лежащий поверх ленты. Под ней видно продолжение снимка,
   и лента этим сама говорит «здесь есть что листать дальше».

   Что именно скопировано:
     · капсула со скруглением в половину высоты, а не прямоугольник с
       закруглёнными углами — на глаз это разные формы;
     · матовое стекло с подъёмом насыщенности: цвет фотографии проступает
       сквозь панель, и она не выглядит наклейкой;
     · светлый волосяной кант по краю — он отделяет стекло от тёмного кадра,
       без него панель растворяется в снимке;
     · активная вкладка — не цвет иконки, а пилюля-подложка под ней.
       Цветом одну иконку из пяти найти трудно, формой — мгновенно.

   Подписи убраны сознательно. Пять слов капслоком — это пять слов, которые
   читают один раз, а потом узнают вкладки по иконкам. Место, которое они
   занимали, отдано снимку.

   Капсула поднята выше зоны домашнего индикатора целиком: на iOS шов стекла
   приходился ровно на её линию, и панель там моргала.
   ============================================================================= */
:root{
  /* Стекло панели держится на своих переменных, а не на общем --glass:
     у накладной панели поверх фотографии и у плашки обновления разные
     требования к плотности, и связывать их одним значением нельзя. */
  --bar-bg:rgba(27,30,34,.72);
  --bar-ring:rgba(255,255,255,.16);
  --bar-pill:rgba(255,255,255,.13);
  --bar-shadow:0 8px 28px rgba(0,0,0,.55);
  /* Тень у карточки под пальцем. Чёрная тень на пергаменте выглядит грязью,
     поэтому значение живёт в переменной, а не числом в правиле. */
  --drag-shadow:0 8px 24px rgba(0,0,0,.5);
}
[data-theme="light"]{
  /* Светлая панель — почти бумага, но всё-таки стекло: сквозь неё должен
     проступать снимок, иначе капсула читается как вырезанный из ленты кусок.
     Плотность взята у навигации набора: rgba(250,250,248,.85) с размытием. */
  --bar-bg:rgba(246,247,248,.76);
  --bar-ring:rgba(16,18,20,.10);
  --bar-pill:rgba(16,18,20,.08);
  --bar-shadow:0 6px 20px rgba(16,18,20,.12);
  --drag-shadow:0 8px 24px rgba(16,18,20,.14);
}

.tabbar{
  /* Накладная поверх документа: лента продолжается под ней. Отступ снизу
     у списка равен высоте панели — последняя карточка не прячется.
     Высоту меряет app.js и кладёт в --tabbar-h: плотность может меняться,
     а отступ должен совпадать всегда. */
  position:fixed;left:0;right:0;bottom:0;z-index:40;
  display:grid;grid-template-columns:repeat(5,1fr);
  align-items:center;
  /* Размеры сняты с записи экрана и переведены в точки (iPhone 16 Pro,
     402 pt при 3x): капсула 358x60, поля по 22 с каждой стороны.
     Раньше поля были 13 — панель шире оригинала на 18 pt, и капсула
     переставала читаться как предмет, положенный на ленту: у предмета
     видно, что вокруг него есть место. */
  margin:0 auto;width:calc(100% - 44px);max-width:430px;
  /* 5 по бокам и 10 по горизонтали: подложка активной вкладки отступает
     от края капсулы ровно настолько же, насколько от её верха. */
  padding:5px 10px;
  /* Половина высоты: капсула, а не прямоугольник со скруглением */
  border-radius:9999px;
  background:var(--bar-bg);
  /* Насыщенность поднята вместе с размытием. Одно размытие даёт мутно-серое
     пятно: цвет под стеклом усредняется и обесцвечивается. Возврат
     насыщенности — то, из-за чего материал выглядит стеклом, а не плёнкой. */
  -webkit-backdrop-filter:blur(22px) saturate(180%);
          backdrop-filter:blur(22px) saturate(180%);
  border:1px solid var(--bar-ring);
  box-shadow:var(--bar-shadow);
  /* Свой слой композитора: иначе размытие пересчитывается на каждый кадр
     прокрутки и панель заметно моргает */
  transform:translateZ(0);
  /* Отступ от края: свой плюс ВСЯ зона домашнего индикатора.
     Панель не должна её задевать — там и был шов. */
  /* Отступ снизу — 22 pt, замерено по записи. Здесь и была главная разница
     с оригиналом: мы прибавляли ВСЮ зону домашнего индикатора (34 pt) и
     панель висела в 44 pt от края — на глаз она стояла заметно выше, чем
     у Instagram, и полоса ленты под ней выглядела забытой.

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

     ОСТОРОЖНО. Именно от этой зоны панель когда-то и подняли: на iOS шов
     стекла приходился ровно на линию индикатора, и панель там моргала.
     Если моргание вернётся — причина здесь, и лечится возвратом к
     10px + env(safe-area-inset-bottom). */
  margin-bottom:max(16px, calc(env(safe-area-inset-bottom, 0px) - 12px));
  /* Растём и сжимаемся от нижнего края: при origin по центру капсула на
     сжатии отъезжает вверх от края экрана, и вместе с ней дёргается зазор
     над зоной домашнего индикатора — единственное, что здесь должно
     стоять намертво. */
  transform-origin:50% 100%;
  /* Возврат с перелётом: по записи экрана ширина доходит до 537 при
     нормальных 534 и только потом садится. Перелёт и есть то, из-за чего
     панель читается как предмет с весом, а не как перерисованный прямоугольник.
     На сжатие он не нужен — туда уходим коротко и линейно, за один кадр
     после начала прокрутки. */
  transition:transform .34s cubic-bezier(.34,1.28,.5,1);
}
/* Листают ленту — панель отдаёт ей место. Ровно 0.85: замерено по кадрам,
   ширина падает с 534 до 448. Меньше — панель начинает читаться как
   уезжающая, и палец тянется её поймать. */
.tabbar.tucked{
  transform:translateZ(0) scale(.85);
  transition:transform .16s ease-out;
}
/* Кнопка — только площадь нажатия. Всё видимое живёт на .tab-ic внутри:
   подложка активной вкладки должна быть уже кнопки, иначе пилюли смыкаются
   боками и вместо пяти отдельных вкладок получается полосатая лента. */
.tabbar button{
  background:none;border:none;padding:0;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  -webkit-tap-highlight-color:transparent;
}
.tab-ic{
  display:flex;align-items:center;justify-content:center;
  /* Во всю ячейку и 48 pt высотой — как в оригинале, где подложка занимает
     ячейку целиком (67 из 67.2 pt). Зазор между соседними ячейками не нужен:
     подсвечена всегда ровно одна вкладка, смыкаться нечему. Прежние 60x42
     оставляли вокруг подложки поля, и она читалась как значок внутри
     кнопки, а не как сама кнопка. */
  width:100%;height:48px;border-radius:9999px;
  /* Неактивная вкладка приглушена цветом лишь на ступень, а не в тень.
     Пять едва различимых значков читаются как выключенная панель — и человек
     ищет глазами, куда вообще можно нажать. Разницу между «здесь» и «не здесь»
     держит подложка, а не яркость иконок. */
  color:var(--ash);
  transition:background .18s ease,color .18s ease;
}
/* 24, а не 22: у иконочного шрифта рисунок мельче кегля, и при 22 штрих
   выходил тоньше оригинального — там иконка занимает около 22 pt чернилами,
   а не размером бокса. */
.tab-ic i{font-size:24px;line-height:1;display:block}
/* Активная вкладка: пилюля-подложка и полный контраст иконки.
   Обводка в полпикселя добавляет штриху веса — у большинства иконок
   Bootstrap нет залитой пары, а «жирнее» здесь важнее, чем «залито». */
.tabbar button.active .tab-ic{
  background:var(--bar-pill);
  color:var(--frost);
}
.tabbar button.active .tab-ic i{-webkit-text-stroke:.4px currentColor}
/* Нажатие: подложка появляется раньше, чем переключится экран. Без этого
   палец не получает ответа, и вкладку жмут второй раз. */
.tabbar button:active .tab-ic{background:var(--bar-pill)}

/* Свой аватар вместо иконки профиля — как в ленте Instagram. Лицо среди
   четырёх штриховых значков находится боковым зрением, ещё до того как
   глаз доедет до правого края. */
.tab-ava{
  width:28px;height:28px;border-radius:50%;flex:0 0 auto;
  background-size:cover;background-position:center;
  display:flex;align-items:center;justify-content:center;
  overflow:hidden;
  /* Кольцо рисуем тенью, а не border: border съедает 2 px из 26 и лицо
     заметно мельчает при переключении на вкладку. Тонкое кольцо стоит
     всегда — без него светлый снимок сливается со стеклом панели. */
  box-shadow:0 0 0 1px var(--hairline);
  transition:box-shadow .18s ease;
}
.tabbar button.active .tab-ava{
  box-shadow:0 0 0 2px var(--frost);
}
/* Пока снимок не загружен — обычная иконка человека в том же круге */
.tab-ava i{font-size:20px}
.tab-ava.has-photo i{display:none}

@media (prefers-reduced-motion:reduce){
  .tab-ic,.tab-ava{transition:none}
  /* Класс всё равно не навешивается (проверка стоит в app.js), но правило
     оставляем: если панель застали поджатой в момент смены настройки,
     она должна просто оказаться на месте, а не доезжать. */
  .tabbar,.tabbar.tucked{transition:none;transform:translateZ(0)}
}

/* ── кнопки ───────────────────────────────────────────────────────────────── */
.pill{
  display:inline-flex;align-items:center;justify-content:center;gap:.5em;
  border-radius:9999px;border:1px solid var(--frost);background:transparent;
  color:var(--frost);font:400 13px/1 var(--f-body);
  letter-spacing:.1em;text-transform:uppercase;
  padding:14px 28px;cursor:pointer;transition:background .18s,color .18s;
}
.pill:hover{background:var(--frost);color:var(--void)}
.pill.solid{background:var(--frost);color:var(--void)}
.pill.solid:hover{background:var(--ash)}
/* Блочный уровень, а не inline-flex: у строчных элементов margin:auto
   не центрирует. Внутри слоёв каждый прямой потомок центруется правилом
   .sheet-body>* — и кнопка во всю ширину оставалась прижатой к левому краю,
   пока поля стояли по центру. На телефоне не видно: до предела в 720px
   ширина не доходит, поля и кнопка одинаково растянуты. Вылезает только
   на широком экране. */
.pill.wide{width:100%;display:flex}
.pill.small{padding:10px 18px;font-size:11px}
.pill.danger{border-color:var(--danger);color:var(--danger)}
.pill.danger:hover{background:var(--danger);color:var(--void)}
.pill:disabled{opacity:.4;cursor:not-allowed}
.ghost{
  background:none;border:none;padding:0;color:var(--mist);cursor:pointer;
  font:400 13px/1.4 var(--f-body);
}
.ghost:hover{color:var(--frost)}

/* ── формы ────────────────────────────────────────────────────────────────── */
.field{margin-bottom:16px}
.field label{
  display:block;font:500 10px/1 var(--f-body);letter-spacing:.18em;
  text-transform:uppercase;color:var(--graphite);margin-bottom:8px;
}
/* Поле — поверхность, а не подчёркивание.

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

   Подложка решает обе задачи разом: поле видно до касания, и видно, что
   оно пустое. */
.field input,.field select,.field textarea{
  width:100%;color:var(--frost);
  background:var(--bone);
  border:1px solid var(--hairline-dim);
  border-radius:11px;padding:12px 13px;outline:none;
  transition:border-color .16s,box-shadow .16s,background .16s;
}
.field input::placeholder,.field textarea::placeholder{color:var(--graphite)}
/* Активное поле подсвечено акцентом — единственное место в форме, где он
   уместен: он показывает, куда именно уходит ввод. */
.field input:focus,.field select:focus,.field textarea:focus{
  border-color:var(--acid);
  box-shadow:0 0 0 3px var(--acid-dim);
  background:var(--paper);
}
.field textarea{resize:vertical;min-height:82px;line-height:1.5}
.field select{appearance:none;background-image:none}
.field select option{background:var(--paper);color:var(--frost)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:end}
/* Элемент сетки по умолчанию не сжимается уже своего содержимого
   (min-width:auto), а у нативного <input type="date"> содержимое — это вся
   строка «18 авг. 2026 г.» вместе с системными отступами. Колонка честно
   растягивалась под неё и выдавливала соседнее поле за край экрана: на
   «Новой заточке» дата наезжала на время, на «Новой бутылке» — на объём.

   min-width:0 снимает это правило: колонка снова слушается 1fr, а длинная
   дата ужимается внутри своего поля. */
.row2>*{min-width:0}
.row2 input,.row2 select{min-width:0;max-width:100%}
/* Нативный вид поля даты на iOS шире, чем нужно, даже при min-width:0 —
   системная кнопка календаря добавляет свои отступы. Снимаем оформление
   и оставляем своё: подложка и радиус уже заданы в .field. */
.field input[type="date"]{
  -webkit-appearance:none;appearance:none;
  text-align:left;
}
.field input[type="date"]::-webkit-date-and-time-value{text-align:left;margin:0}
.field input[type="date"]::-webkit-calendar-picker-indicator{
  opacity:.45;filter:invert(var(--pick-invert,1));cursor:pointer;padding:0;margin:0;
}

/* Калькулятор: три поля в ряд на широких экранах, в столбик на узких.
   align-items:end держит поля ввода на одной линии, даже если подпись
   переносится на две строки. */
.calc-fields{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:end}
.calc-fields .field{margin-bottom:0}
.calc-fields.three{grid-template-columns:1fr 1fr 1fr}
@media(max-width:520px){
  .calc-fields.three{grid-template-columns:1fr 1fr}
  .calc-fields.three .field:first-child{grid-column:1 / -1}
}

/* чипы выбора (камни, продукты) */
.chips{display:flex;flex-wrap:wrap;gap:8px}
/* Чип на подложке, а не на прозрачном фоне. Список камней — это два-три
   десятка пилюль подряд; из одних контуров получалась проволочная сетка,
   в которой глаз не находил, что уже выбрано. Заливка отделяет выбранные
   от остальных без чтения. */
.chip{
  border:1px solid var(--hairline-dim);border-radius:9999px;
  background:var(--bone);
  color:var(--ash);font:400 12px/1 var(--f-mono);padding:10px 15px;cursor:pointer;
  transition:background .16s,border-color .16s,color .16s;
}
.chip:hover{border-color:var(--hairline);color:var(--frost)}
.chip.on{background:var(--frost);border-color:var(--frost);color:var(--void)}
/* Порядковый номер шага — акцентом на своей подложке. Прежний вариант
   (то же начертание, приглушённое на 40%) читался как часть названия камня,
   а это единственное, что превращает набор пилюль в последовательность. */
.chip .ord{
  display:inline-block;margin-right:7px;font:600 9px/1 var(--f-mono);
  padding:3px 5px;border-radius:5px;
  background:var(--acid-dim);color:var(--acid);vertical-align:middle;
}
.chip.on .ord{background:rgba(0,0,0,.14);color:var(--void)}

/* ── список журнала ───────────────────────────────────────────────────────── */
/* Запись журнала — объект, а не строка списка. Разница не косметическая:
   у записи внутри четыре разных уровня (нож, дата, углы, набор камней,
   заметка, миниатюры), и без рамки они висят в общем потоке вперемешку
   с соседними записями. Рамка говорит, где кончается одна заточка
   и начинается другая, — линейка этого не говорит. */
.entry{
  padding:15px 15px 16px;margin-bottom:10px;cursor:pointer;
  display:block;width:100%;text-align:left;color:inherit;
  background:var(--paper);border:1px solid var(--hairline-dim);border-radius:15px;
  transition:border-color .16s;
}
.entry:hover{border-color:var(--hairline)}
.entry:last-child{margin-bottom:0}
.entry-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px}
.entry-title{font:600 16px/1.25 var(--f-body);color:var(--frost)}
.entry-date{font:400 11px/1 var(--f-mono);color:var(--mist);white-space:nowrap;margin-left:auto}
/* Метка публикации. Отличать «лежит в журнале» от «висит в ленте» нужно
   с одного взгляда: это разные степени открытости, и человек должен видеть,
   какие из его записей видны чужим. */
.entry-live{
  font:600 9px/1 var(--f-body);letter-spacing:.14em;text-transform:uppercase;
  color:var(--acid-text);background:var(--acid-wash);
  border:1px solid var(--acid-line);border-radius:999px;padding:4px 8px;
  white-space:nowrap;flex:0 0 auto;
}
.entry-meta{
  display:flex;flex-wrap:wrap;align-items:center;gap:9px;margin-top:9px;
  font:500 14px/1 var(--f-mono);color:var(--frost);letter-spacing:-.01em;
}
.entry-meta i{color:var(--graphite);font-style:normal;font-size:11px}
.entry-note{
  margin-top:8px;font:400 13px/1.45 var(--f-body);color:var(--mist);
}
.entry-stones{margin-top:9px;font:400 12px/1.5 var(--f-mono);color:var(--ash)}
.entry-thumbs{display:flex;gap:6px;margin-top:11px}
.entry-thumbs img{width:46px;height:46px;object-fit:cover;border-radius:8px;
  box-shadow:inset 0 0 0 1px var(--sheen)}

/* ── карточки ножей и камней ──────────────────────────────────────────────── */
/* То же, что с записями журнала: нож и камень — предметы, у каждого своё
   имя, сталь, твёрдость, сведение. Список строк превращал арсенал
   в выгрузку таблицы; карточка возвращает предметам вещественность. */
.card{
  padding:14px 15px;margin-bottom:8px;border-radius:14px;
  background:var(--paper);border:1px solid var(--hairline-dim);
  display:flex;justify-content:space-between;align-items:flex-start;gap:14px;
}
.card:last-of-type{margin-bottom:0}
.card-title{font:600 15px/1.3 var(--f-body);color:var(--frost)}
.card-sub{font:400 12px/1.4 var(--f-mono);color:var(--mist);margin-top:5px}

/* ── фото ─────────────────────────────────────────────────────────────────── */
.photo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:8px}
/* Превью тем же кадром, что и в ленте: квадратное превью обещало один кадр,
   а в ленте появлялся другой — человек видел не то, что выбирал. */
.photo-cell{position:relative;aspect-ratio:4/5;background:var(--bone);border:1px solid var(--hairline-dim);border-radius:10px;overflow:hidden}
.photo-cell img{width:100%;height:100%;object-fit:cover;display:block}
.photo-cell .del{
  position:absolute;top:4px;right:4px;width:24px;height:24px;border-radius:50%;
  background:var(--overlay);color:var(--frost);border:none;cursor:pointer;font-size:12px;
  display:flex;align-items:center;justify-content:center;
}
.photo-add{
  aspect-ratio:4/5;border-radius:10px;border:1px dashed var(--hairline);background:none;color:var(--mist);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
  cursor:pointer;font:400 10px/1 var(--f-body);letter-spacing:.1em;text-transform:uppercase;
}
.photo-add:hover{border-color:var(--frost);color:var(--frost)}
.photo-add i{font-size:20px}
.upload-note{font:400 11px/1.5 var(--f-mono);color:var(--mist);margin-top:9px}

/* ── вход ─────────────────────────────────────────────────────────────────── */
/* Экран входа сам управляет своим display.
   Раньше .login{display:flex} перебивал .view{display:none} — экран не скрывался;
   а после добавления .active побеждало .view.active{display:block}, и flex-центровка
   слетала, из-за чего блок уезжал влево. Селекторы из двух и трёх классов
   расставляют приоритеты однозначно. */
.login.view{display:none}
.login.view.active{
  display:flex;min-height:100svh;
  align-items:center;justify-content:center;
  padding:var(--pad-x);
}
.login-inner{max-width:420px;width:100%}
.login h1{font:700 clamp(30px,9vw,56px)/0.96 var(--f-display);
  text-transform:uppercase;color:var(--frost);letter-spacing:-.02em;margin:0 0 18px}
.login p{color:var(--ash);margin:0 0 30px}
.tg-step{
  border-top:1px solid var(--hairline);margin-top:26px;padding-top:20px;
  font:400 13px/1.6 var(--f-body);color:var(--mist);
}
.tg-step ol{margin:10px 0 0;padding-left:18px}
.tg-step li{margin-bottom:6px}

/* ── статусы ──────────────────────────────────────────────────────────────── */
/* Пустое состояние — не «ничего нет», а приглашение. Прежний вариант
   (иконка на половинной прозрачности и серая строка посреди пустого экрана)
   выглядел как ошибка загрузки. Рамка и подложка превращают пустоту
   в место, которое ждёт содержимого. */
.empty{
  padding:34px 22px;text-align:center;color:var(--mist);
  background:var(--paper);border:1px solid var(--hairline-dim);
  border-radius:16px;
}
.empty i{font-size:24px;display:block;margin-bottom:12px;color:var(--graphite)}
.toast{
  /* Над панелью вкладок, а не на фиксированной высоте. --nav-h — это 60 px,
     которые не знают ни про реальную высоту капсулы, ни про зону домашнего
     индикатора. После того как панель похудела до 54 px, сообщение садилось
     ей на плечо. --tabbar-h меряет app.js, и отступ совпадает всегда. */
  position:fixed;left:50%;
  bottom:calc(var(--tabbar-h, 56px) + 26px + env(safe-area-inset-bottom, 0px));
  transform:translateX(-50%);
  background:var(--frost);color:var(--void);padding:12px 22px;border-radius:9999px;
  font:500 13px/1 var(--f-body);z-index:100;opacity:0;pointer-events:none;
  transition:opacity .2s;white-space:nowrap;max-width:90vw;
}
.toast.show{opacity:1}
.toast.err{background:var(--danger);color:#fff}
.spinner{
  width:18px;height:18px;border:2px solid var(--hairline);border-top-color:var(--frost);
  border-radius:50%;animation:spin .7s linear infinite;display:inline-block;
}
@keyframes spin{to{transform:rotate(360deg)}}
.bar{height:2px;background:var(--hairline);overflow:hidden;margin-top:10px}
.bar span{display:block;height:100%;background:var(--frost);width:0;transition:width .25s}

/* ── модальный слой формы ─────────────────────────────────────────────────── */
.sheet{
  position:fixed;inset:0;z-index:60;background:var(--void);
  display:none;
  /* Колонка: шапка закреплена, прокручивается только тело.
     Раньше прокручивался весь слой, а шапка держалась на sticky —
     на iOS она подрагивала и «уезжала» при инерции. */
  flex-direction:column;
}
.sheet.open{display:flex}

.sheet-head{
  flex:0 0 auto;                       /* не сжимается, всегда на месте */
  display:flex;align-items:center;justify-content:space-between;
  /* В режиме приложения уходим из-под статус-бара */
  padding:calc(16px + env(safe-area-inset-top,0px)) var(--pad-x) 16px;
  border-bottom:1px solid var(--hairline-dim);
  background:var(--void);
}
.sheet-title{font:700 14px/1 var(--f-display);text-transform:uppercase;color:var(--frost)}

.sheet-body{
  flex:1 1 auto;
  overflow-y:auto;
  /* Без этого iOS не отдаёт слою прокрутку, пока не коснёшься содержимого */
  -webkit-overflow-scrolling:touch;
  /* Прокрутка не «протекает» на страницу под слоем */
  overscroll-behavior:contain;
  padding:24px var(--pad-x) calc(40px + env(safe-area-inset-bottom,0px));
}
/* Центрирование прямых потомков слоя. Класс продублирован нарочно: без этого
   правила вроде .hint{margin:...} и h2.section{margin:...}, объявленные ниже,
   перебивают margin:auto — и абзац прилипает к левому краю, пока соседние
   поля стоят по центру. На телефоне не видно, вылезает на широком экране. */
.sheet-body.sheet-body>*{max-width:720px;margin-left:auto;margin-right:auto}
.x{background:none;border:none;color:var(--frost);font-size:20px;cursor:pointer;line-height:1}

/* ── профиль ──────────────────────────────────────────────────────────────── */
.stat{border-top:1px solid var(--hairline);padding:16px 0;display:flex;
  justify-content:space-between;align-items:baseline;gap:12px}
.stat:last-of-type{border-bottom:1px solid var(--hairline)}
.stat .k{font:400 11px/1 var(--f-body);letter-spacing:.18em;text-transform:uppercase;color:var(--mist);
  flex:0 0 auto}
/* На широком экране строка помещается целиком, и выравнивание незаметно.
   На телефоне значение переносится на вторую и третью строку — и без
   text-align каждая из них прижималась к левому краю своего блока, то есть
   висела посреди строки без опоры. Правый край даёт колонке ровную границу,
   а перенос по разделителю «·» не рвёт числа пополам. */
.stat .v{font:400 14px/1.45 var(--f-mono);color:var(--frost);
  flex:1 1 auto;min-width:0;text-align:right;overflow-wrap:anywhere}

@media(min-width:768px){
  /* Капсула по содержимому: раньше колонки фиксировались по 132 px (660 всего),
     а ширина оставалась «во всю строку» — панель вылезала за правый край.
     Без подписей колонка сузилась до иконки с полями. */
  .tabbar{grid-template-columns:repeat(5,68px);width:max-content;max-width:calc(100% - 44px)}
  .wrap{padding-top:34px}
}

/* ── Калькулятор и конвертер ──────────────────────────────────────────────── */
.calc-out{
  border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline);
  padding:22px 0 18px;margin-bottom:26px;
  display:flex;align-items:baseline;gap:10px;
}
.calc-value{
  font:700 clamp(46px,14vw,86px)/0.9 var(--f-display);
  letter-spacing:-.03em;color:var(--frost);
}
.calc-unit{font:400 13px/1 var(--f-mono);color:var(--mist);
  text-transform:uppercase;letter-spacing:.12em}

.grit-table{margin-bottom:18px}
.grit-row{
  display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  border-top:1px solid var(--hairline-dim);padding:12px 0;
}
.grit-row:last-child{border-bottom:1px solid var(--hairline-dim)}
.grit-row .k{font:400 10px/1 var(--f-body);letter-spacing:.2em;
  text-transform:uppercase;color:var(--mist)}
.grit-row .v{font:500 17px/1 var(--f-mono);color:var(--frost)}

.grit-range{
  width:100%;appearance:none;background:transparent;height:32px;cursor:pointer;
}
.grit-range::-webkit-slider-runnable-track{height:1px;background:var(--hairline)}
.grit-range::-moz-range-track{height:1px;background:var(--hairline)}
.grit-range::-webkit-slider-thumb{
  appearance:none;width:22px;height:22px;border-radius:50%;
  background:var(--frost);margin-top:-11px;
}
.grit-range::-moz-range-thumb{
  width:22px;height:22px;border:none;border-radius:50%;background:var(--frost);
}

/* подсказка под полем */
.hint{margin:8px 0 0;font:400 12px/1.45 var(--f-body);color:var(--mist)}
.hint a{color:var(--frost);text-decoration:underline;text-underline-offset:2px}

/* ── Жидкости ─────────────────────────────────────────────────────────────── */
.bottle{
  border-top:1px solid var(--hairline);padding:16px 0;
  display:flex;justify-content:space-between;align-items:flex-start;gap:14px;
}
.bottle:last-of-type{border-bottom:1px solid var(--hairline)}
.bottle-name{font:600 15px/1.3 var(--f-body);color:var(--frost)}
.bottle-sub{font:400 12px/1.5 var(--f-mono);color:var(--mist);margin-top:5px}
.bottle.done .bottle-name{color:var(--mist)}
.bottle-act{display:flex;flex-direction:column;gap:8px;align-items:flex-end;flex-shrink:0}
.bottle-warn{
  margin-top:9px;font:400 12px/1.5 var(--f-body);color:var(--ash);
}
.bottle-warn a{color:var(--frost);text-decoration:underline;text-underline-offset:2px}

/* ── Выбор смахиванием ────────────────────────────────────────────────────
   Горизонтальная лента с прилипанием: листаешь пальцем, выбранным становится
   тот вариант, что оказался по центру. Тап тоже работает. */
.swipe{
  position:relative;
  display:flex;gap:10px;
  overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x proximity;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  padding:4px 0 6px;
  /* поля по краям, чтобы крайние варианты доезжали до центра */
}
.swipe::-webkit-scrollbar{display:none}
.swipe-opt{
  flex:0 0 auto;scroll-snap-align:center;
  border:1px solid var(--hairline);border-radius:9999px;
  background:transparent;color:var(--mist);
  font:400 13px/1 var(--f-body);white-space:nowrap;
  padding:13px 20px;cursor:pointer;
  transition:color .18s,border-color .18s,background .18s;
}
.swipe-opt.on{
  background:var(--frost);border-color:var(--frost);color:var(--void);
  font-weight:500;
}
.swipe::after{content:'';flex:0 0 12px}

/* ── Просмотр фото во весь экран ──────────────────────────────────────────── */
.viewer{
  position:fixed;inset:0;z-index:80;display:none;
  background:var(--overlay);
  align-items:center;justify-content:center;
  padding:env(safe-area-inset-top,0px) 0 env(safe-area-inset-bottom,0px);
}
.viewer.open{display:flex}
.viewer img{max-width:100%;max-height:100%;object-fit:contain;display:block}
.viewer-close{
  position:absolute;top:calc(14px + env(safe-area-inset-top,0px));right:16px;
  width:42px;height:42px;border-radius:50%;
  background:var(--raise);border:none;color:var(--frost);
  font-size:24px;line-height:1;cursor:pointer;z-index:2;
}
.viewer-count{
  position:absolute;bottom:calc(20px + env(safe-area-inset-bottom,0px));
  left:50%;transform:translateX(-50%);
  font:400 12px/1 var(--f-mono);color:var(--mist);
}
.photo-cell img{cursor:zoom-in}

/* =============================================================================
   ЛЕНТА, ПРОФИЛИ ЗАТОЧНИКОВ, РЕЙТИНГ
   Тот же язык: чёрный холст, hairline вместо панелей, моноширинные цифры.
   Карточка не «плитка» с рамкой, а полоса контента — так на телефоне
   помещается больше сути и меньше декора.
   ============================================================================= */

/* Шапка ленты — одна строка: слева переключатель «Свежее / Лучшее»,
   справа события и рейтинг. Раньше здесь стоял заголовок в два этажа
   («СООБЩЕСТВО», «ЛЕНТА»), а таблетки шли отдельной строкой под ним —
   три горизонтали подряд ради одного выбора.

   align-items:center, а не flex-start: таблетка ниже круглой кнопки
   (34 против 40), и по верхнему краю они вставали ступенькой. По центру
   обе строки читаются как одна линия — этого и добивались. */
.feed-head{
  display:flex;align-items:center;justify-content:space-between;
  gap:16px;margin-bottom:10px;
  /* Своё поле: у ленты его нет, а строке выбора оно нужно — иначе
     таблетка «Свежее» упирается в край экрана. */
  padding:0 14px;
}
/* Полоса с таблетками прижата к шапке. Пока над ней стоял заголовок, воздух
   сверху был частью композиции — заголовку положено дышать. Без заголовка
   те же 26 px превратились в пустой пояс: строка висела посреди ничего,
   а первая карточка начиналась почти на середине экрана.

   Правило по id, а не через .wrap: остальным разделам верхний воздух нужен,
   у них заголовки на месте. И медиазапрос на 768 px, который поднимает
   .wrap до 34 px, id перебивает без !important — по весу селектора. */
/* У ленты своих полей нет вовсе: снимок идёт до края экрана. Текстовые
   части записи держат отступ сами (см. .fcard-top и соседей ниже) — так
   поле есть там, где оно нужно строке, и его нет там, где оно отнимало бы
   у фотографии.

   Ширина ограничена 520 px: на настольном экране лента без ограничителя
   растягивается на всю страницу, и кадр 4:5 вырастает в полотно на два
   экрана в высоту. 520 — та же мера, что у ленты в приложениях, откуда
   сюда приходят. */
#view-feed{
  padding:6px 0 0;
  max-width:520px;
}
/* Таблетки в этой строке чуть плотнее общих: рядом с кружками в 40 px
   пилюля в 34 выглядела раздутой. Трогаем только здесь — в формах у чипов
   своя работа и свой размер нажатия. */
.feed-head .chip{padding:8px 16px}
/* Не переносится: два-три варианта сортировки должны остаться в строке
   с кнопками, даже если появится третий. */
.feed-head .chips{flex-wrap:nowrap}
.feed-tools{display:flex;gap:8px;flex:0 0 auto}
/* Круглые кнопки на подложке, как чипы рядом: пустой контур на холсте
   читался как место, где что-то не догрузилось. */
.icon-btn{
  position:relative;width:40px;height:40px;border-radius:50%;
  background:var(--bone);border:1px solid var(--hairline-dim);color:var(--ash);
  font-size:16px;cursor:pointer;transition:.15s;
}
.icon-btn:hover{border-color:var(--hairline);color:var(--frost)}
.icon-btn .dot{
  position:absolute;top:7px;right:7px;width:7px;height:7px;border-radius:50%;
  background:var(--acid);box-shadow:0 0 0 2px var(--void);
}

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

   Рамку тут уже пробовали — и она честно решала свою задачу: показывала,
   где кончается одна заточка и начинается другая. Но платой за неё была
   ширина: поле с каждой стороны плюс собственный отступ карточки съедали
   6% экрана, а на кадре 4:5 это ещё и высота. На телефоне, где снимок
   и есть содержание, отдавать проценты под декор нельзя.

   Разделение осталось, просто держится не рамкой, а тремя вещами сразу:
   зазором между записями, линией во всю ширину и краем самого снимка
   (--sheen, см. .shot). Этого хватает — исходная беда была не в отсутствии
   рамки, а в том, что тёмное макро вытекало в чёрный холст без границы. */
.fcard{
  margin:0 0 6px;
  padding:0 0 4px;
  border-bottom:1px solid var(--hairline);
}
.fcard:last-child{border-bottom:none}
.fcard-top,
.fcard-body,
.fcard-acts,
.fcard-bare{padding-left:14px;padding-right:14px}

.fcard-top{
  display:flex;align-items:center;gap:10px;width:100%;
  background:none;border:none;cursor:pointer;text-align:left;
  /* Поля те же, что у нижней части карточки: имя и время должны стоять
     по одной линии с характеристиками, а не липнуть к краю. Фотография —
     единственное, что идёт от края до края КАРТОЧКИ. */
  padding:12px 14px;
}
.ava{
  width:34px;height:34px;border-radius:50%;object-fit:cover;flex:0 0 auto;
  background:var(--graphite);
  /* Та же обводка, что у снимка, и по той же причине: аватары в этой ленте
     сплошь тёмные — мастерская, металл, станок. Без края круг на холсте
     выглядит дырой, а не портретом. */
  box-shadow:inset 0 0 0 1px var(--sheen);
}
/* Цвет буквы задан жёстко, а не через --frost: подложка у запасного аватара
   тёмная в обеих темах, и в светлой переменная дала бы почти чёрный текст
   на почти чёрном круге. Это единственное место, где тема ни при чём. */
.ava.letter{
  display:flex;align-items:center;justify-content:center;
  font:600 12px/1 var(--f-body);color:#fff;letter-spacing:.02em;
}
/* Крупный аватар: 216 px вместо 72. Лицо человека — первое, что видно
   на его странице, и в прежнем размере оно читалось как значок рядом
   с текстом, а не как портрет. */
.ava.big{width:150px;height:150px}
.ava.big.letter{font-size:58px}
/* В столбик, а не в строку: имя и его регалии — разные уровни, и на узком
   экране в одну строку они не помещались, из-за чего имя обрезалось
   многоточием при живом свободном месте под ним. */
.fcard-who{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}
.fcard-name{font:600 14px/1.2 var(--f-body);color:var(--frost);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fcard-sub{font:400 11px/1 var(--f-mono);color:var(--graphite)}
.fcard-sub i{font-style:normal;padding:0 2px}
.fcard-stars{font-size:11px;color:var(--mist);white-space:nowrap}
.fcard-ago{margin-left:auto;font-size:11px;color:var(--graphite);white-space:nowrap}



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

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

   Разница в весе делает то, чего не сделает никакая палитра: взгляд находит
   нужное, не читая всё. */
.fcard-body{margin-top:12px}
.fcard-knife{font:600 15px/1.3 var(--f-body);color:var(--frost)}
.fcard-caption{margin:7px 0 0;color:var(--ash)}
.fcard-specs{
  margin-top:2px;font:500 15px/1 var(--f-mono);color:var(--frost);
  display:flex;flex-wrap:wrap;gap:9px;align-items:center;letter-spacing:-.01em;
}
.fcard-specs i{color:var(--graphite);font-style:normal;font-size:12px}
.fcard-stones{margin-top:10px;font:400 12px/1.55 var(--f-mono);color:var(--ash)}
.fcard-coolant{margin-top:8px;font:400 11px/1 var(--f-mono);color:var(--graphite)}
.fcard-note{margin:11px 0 0;font-size:13px;color:var(--mist);
  border-left:2px solid var(--hairline);padding-left:11px}

/* Отбита линией: действия — это не продолжение записи, а то, что можно
   с ней сделать. Пока строка просто висела под текстом, счётчики читались
   как ещё одна характеристика заточки. */
.fcard-acts{
  display:flex;align-items:center;gap:18px;
  margin-top:12px;padding-top:4px;padding-bottom:2px;
  border-top:1px solid var(--hairline-dim);
}
.fcard-acts .spacer{flex:1 1 auto}
.fact{
  display:inline-flex;align-items:center;gap:6px;
  background:none;border:none;padding:6px 0;cursor:pointer;
  color:var(--mist);font-size:15px;transition:color .15s;
}
.fact span{font-size:12px}
.fact:hover{color:var(--frost)}
.fact.on{color:var(--frost)}
.fact.static{cursor:default;color:var(--graphite)}
.fact.static.mine{font-size:11px;letter-spacing:.12em;text-transform:uppercase}
.fact:disabled{opacity:.35;cursor:default}
.feed-end{text-align:center;color:var(--graphite);font-size:12px;padding:26px 0 8px}

/* ── профиль заточника ───────────────────────────────────────────────────── */
.ghost.back{
  background:none;border:none;padding:0 0 18px;cursor:pointer;
  color:var(--mist);font:400 12px/1 var(--f-body);letter-spacing:.1em;
  text-transform:uppercase;
}
.ghost.back:hover{color:var(--frost)}

.person-head{text-align:center;padding:6px 0 4px}
.person-head .ava{margin:0 auto 16px;display:block}
.person-head .ava.letter{display:flex}
h1.display.person-name{font-size:clamp(24px,6vw,38px);margin:0 0 6px}
.person-bio{max-width:520px;margin:12px auto 0;color:var(--ash)}

/* Четыре числа — витрина заточника, первое, что видит гость на его странице.
   Между двумя линейками они читались как строка таблицы: ряд цифр, набранных
   одинаково и висящих в пустоте. Плитка даёт каждому числу собственную
   площадь, а вся четвёрка складывается в один блок. */
.person-nums{
  display:grid;grid-template-columns:repeat(4,1fr);gap:6px;
  margin:22px 0 24px;padding:0;border:none;
}
.pnum{
  display:flex;flex-direction:column;align-items:center;gap:6px;
  padding:14px 4px 12px;border-radius:13px;
  background:var(--paper);border:1px solid var(--hairline-dim);
}
.pnum .v{font:500 22px/1 var(--f-mono);color:var(--frost);letter-spacing:-.02em}
/* Пустое значение — прочерк, и он не должен весить столько же, сколько
   заработанное число. Тем же кеглем, но приглушённым цветом: строка
   остаётся ровной, а взгляд собирает из неё только то, что есть. */
.pnum .v.zero{color:var(--graphite)}
.pnum .k{font-size:9px;letter-spacing:.13em;text-transform:uppercase;color:var(--graphite)}

.arsenal{
  background:var(--paper);border:1px solid var(--hairline-dim);
  border-radius:15px;padding:2px 14px;
}
.ars-row{
  display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  padding:11px 0;border-bottom:1px solid var(--hairline-dim);
}
.ars-row:last-child{border-bottom:none}
.ars-name{color:var(--frost);font-size:14px}
.ars-grit{font-size:12px;color:var(--mist);white-space:nowrap}

/* ── рейтинг ─────────────────────────────────────────────────────────────── */
.top-row{
  display:flex;align-items:center;gap:12px;width:100%;
  padding:12px 0;background:none;border:none;border-bottom:1px solid var(--hairline-dim);
  cursor:pointer;text-align:left;
}
.top-place{width:26px;flex:0 0 auto;font-size:13px;color:var(--graphite)}
.top-who{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1 1 auto}
.top-sub{font-size:11px;color:var(--mist)}
.top-score{font-size:14px;color:var(--frost)}

/* ── тумблер ─────────────────────────────────────────────────────────────── */
.toggle-row{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:13px 0;border-bottom:1px solid var(--hairline-dim);
}
.toggle{
  position:relative;flex:0 0 auto;width:44px;height:26px;border-radius:13px;
  background:none;border:1px solid var(--hairline);cursor:pointer;transition:.18s;
}
.toggle::after{
  content:'';position:absolute;top:3px;left:3px;width:18px;height:18px;
  border-radius:50%;background:var(--graphite);transition:.18s;
}
.toggle.on{background:var(--frost);border-color:var(--frost)}
.toggle.on::after{left:21px;background:var(--void)}
/* Строка времени на странице заточника: те же поля и тот же ритм, что
   у шапки карточки в ленте. Без верхнего отступа время липло к предыдущей
   записи. */
.fcard-bare{padding:6px var(--pad-x) 2px}
.fcard-bare .fcard-ago{margin-left:0}

/* Кнопки-иконки обязаны сохранять площадь нажатия, даже если шрифт иконок
   не доехал (CDN недоступен, связь легла). Без этого кнопка жалобы
   схлопывается в ноль пикселей, и палец попадает в пустоту. */
.fact{min-width:34px;min-height:36px;justify-content:center}
.fact.like,.fact.static{justify-content:flex-start}

/* =============================================================================
   АВАТАРЫ И НАГРАДЫ
   ============================================================================= */

/* Аватар может быть картинкой в фоне (свой снимок или фото из Telegram)
   либо буквой имени — запасной вариант, когда ни того, ни другого нет. */
.ava{background-size:cover;background-position:center;background-repeat:no-repeat}

.ava-edit{display:flex;align-items:center;gap:18px;margin:0 0 30px}
.ava-edit .ava.big{flex:0 0 auto}
.ava-edit-acts{display:flex;flex-direction:column;align-items:flex-start;gap:8px}
.ava-edit-acts .upload-note{margin:2px 0 0}

/* ── Награды плитками ─────────────────────────────────────────────────────
   Полоса, которая прокручивается вбок. Плитка у края обрезается намеренно:
   обрезанный объект — единственная честная подсказка, что там есть
   продолжение. Именно этого не хватало бегущим строкам: их обрывки
   («…нте», «…очек в журнале») читались как поломка вёрстки.               */
.aw-strip{
  position:relative;
  /* Полоса выходит за поля страницы: обрезанная плитка должна упираться
     в край экрана, а не в невидимую границу контейнера.

     flex-база и min-width обязательны. Полоса лежит внутри .awards, а это
     флексбокс: без них элемент не сжимается уже своего содержимого, и сумма
     всех плиток становится шириной блока — страница уезжает вправо целиком
     вместо того, чтобы прокручивалась одна полоса. Ровно это и было видно
     в профиле: боковой прокрутки нет, а весь экран сдвинут. */
  flex:1 1 100%;min-width:0;
  margin:14px calc(var(--pad-x) * -1) 4px;
}
/* Привязка к плиткам убрана намеренно.

   scroll-snap-align выравнивает элемент по snapport, а snapport строится
   от padding box контейнера — то есть ВКЛЮЧАЕТ его поля. Первая плитка
   честно вставала «в начало» и тем самым съедала левый отступ: полоса
   начиналась вплотную к краю экрана, хотя padding в 20 px был на месте.

   Наградам привязка и не нужна: это не карусель кадров, где нужно
   останавливаться ровно на снимке, а полоса, которую листают глазами. */
.aw-run{
  display:flex;gap:9px;overflow-x:auto;
  padding:2px var(--pad-x);
  scrollbar-width:none;-webkit-overflow-scrolling:touch;
}
.aw-run::-webkit-scrollbar{display:none}
/* Прокрутка вбок — на всех ширинах.

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

   Прокрутка колесом работает: у горизонтального контейнера браузеры
   перекладывают вертикальное колесо в горизонтальную прокрутку, а на
   трекпаде это привычный жест вбок. */
/* Хвост в конце полосы. Правый padding у прокручиваемого флексбокса
   отрисовывается не во всех движках: в Safari последняя плитка упирается
   в край, будто её обрезали. Псевдоэлемент — единственный способ, который
   работает везде одинаково. */
.aw-run::after{content:'';flex:0 0 calc(var(--pad-x) - 9px);}
.aw-tile{
  flex:0 0 auto;width:114px;
  padding:13px 12px 14px;border-radius:14px;
  background:var(--bone);border:1px solid var(--hairline-dim);
  display:flex;flex-direction:column;
}
.aw-tile i{font-size:17px;color:var(--graphite);line-height:1}
.aw-name{font:600 11.5px/1.25 var(--f-body);color:var(--mist);margin-top:10px}
.aw-hint{font:400 10px/1.35 var(--f-body);color:var(--graphite);margin-top:4px}
/* Полученная — акцентом и на своей подложке. Невзятая остаётся серой:
   разница должна читаться боковым зрением, без чтения подписей. */
.aw-tile.got{background:var(--acid-wash);border-color:var(--acid-line)}
.aw-tile.got i{color:var(--acid)}
.aw-tile.got .aw-name{color:var(--frost)}
/* Медаль вручается руками — контур вдвое толще, как и раньше у значков */
.aw-tile.got.medal{border-width:2px}

.awards{display:flex;flex-wrap:wrap;gap:8px}
.award{
  font-weight:600;
  display:inline-flex;align-items:center;gap:7px;
  padding:8px 14px;border:1px solid var(--hairline);border-radius:100px;
  font-size:12px;color:var(--ash);
}
.award i{font-size:13px;color:var(--mist)}
/* Медаль вручается руками — она и должна отличаться от посчитанной ачивки */
.award.medal{border-color:var(--frost);color:var(--frost)}
.award.medal i{color:var(--frost)}

.award-row{
  display:flex;align-items:center;gap:14px;
  padding:13px 0;border-bottom:1px solid var(--hairline-dim);opacity:.5;
}
.award-row.got{opacity:1}
.award-ic{
  flex:0 0 auto;width:34px;height:34px;border-radius:50%;
  border:1px solid var(--hairline);display:flex;align-items:center;justify-content:center;
  font-size:14px;color:var(--mist);
}
.award-row.got .award-ic{border-color:var(--frost);color:var(--frost)}
.award-txt{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1 1 auto}
.award-name{font:600 14px/1.25 var(--f-body);color:var(--frost)}
.award-hint{font-size:11px;color:var(--mist)}
.award-prog{font-size:12px;color:var(--graphite);white-space:nowrap}

/* ── очередь жалоб ───────────────────────────────────────────────────────── */
.rep-row{padding:15px 0;border-bottom:1px solid var(--hairline-dim)}
.rep-row.hidden{opacity:.55}
.rep-head{display:flex;justify-content:space-between;gap:12px;align-items:baseline}
.rep-who{font:600 14px/1.2 var(--f-body);color:var(--frost)}
.rep-n{font-size:11px;color:var(--danger);white-space:nowrap}
.rep-cap{margin-top:6px;font-size:13px;color:var(--ash)}
.rep-reasons{margin-top:5px;font-size:11px;color:var(--mist)}
.rep-acts{display:flex;gap:10px;flex-wrap:wrap;margin-top:11px}

/* =============================================================================
   СНИМКИ В ЛЕНТЕ — листаются пальцем
   Без открытия в полный экран: в ленте лишний слой только мешает. Прокрутка
   с привязкой к кадру, полоса прокрутки спрятана — жест остаётся нативным,
   а значит инерция и «резинка» работают как везде в системе.
   ============================================================================= */
.shots{position:relative;margin:0}
.shots-track{
  display:flex;overflow-x:auto;scroll-snap-type:x mandatory;
  -webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;
  scrollbar-width:none;
}
.shots-track::-webkit-scrollbar{display:none}
/* Кадр 4:5 задаётся при загрузке (photo-compress.js), поэтому здесь ничего
   не режем: снимок показывается целиком.

   Пропорция сменилась с 3:4 ради того, чтобы карточка помещалась в экран
   целиком. 3:4 при ширине телефона даёт кадр в 130% ширины по высоте —
   вместе с шапкой, характеристиками и строкой реакций запись перестаёт
   влезать куда бы то ни было. 4:5 короче на четверть и остаётся вертикальным:
   нож в руке ложится в него без потерь. Размер и место в дорожке держит
   обёртка .shot, а не сам <img>, и волна ожидания живёт на ней.

   Так пришлось сделать из-за opacity. Снимок появляется плавно, то есть
   стартует с opacity:0 — а прозрачность гасит элемент целиком, вместе
   с его собственным фоном. Волна была прописана на самом <img>, и увидеть
   её было нельзя ни при каких условиях: пока она нужна, элемент невидим,
   а как только он проявился, она уже не нужна. Анимация исправно числилась
   в стилях и исправно не показывалась никому. */
.shot{
  flex:0 0 100%;width:100%;aspect-ratio:4/5;
  scroll-snap-align:center;position:relative;overflow:hidden;
  background-image:var(--shot-bg);
  background-size:280% 100%;
  animation:shot-wave 1.4s linear infinite;
}
/* Край снимка. Макро кромки — это почти всегда тёмное поле с узкой полосой
   света, и на тёмном холсте у такого кадра просто нет границы: он вытекает
   в фон, а следующий пост начинается там же, где кончился предыдущий. На
   скринах ленты из-за этого два снимка подряд читались как один.

   Обводка внутренняя (inset), а не border: рамка снаружи сдвинула бы кадр
   и сломала привязку прокрутки к границе экрана. Толщина в один пиксель
   и восемь процентов непрозрачности — ровно столько, чтобы глаз нашёл
   край, и недостаточно, чтобы заметить линию. */
.shot{box-shadow:inset 0 0 0 1px var(--sheen)}
/* Снимок пришёл — волну гасим: крутить её под готовой картинкой значит
   зря тратить батарею */
.shot.ready{animation:none;background-image:none}

.shots-track img{
  width:100%;height:100%;object-fit:cover;
  display:block;user-select:none;
  /* Появляется плавно, а не вспыхивает. Мигание при обновлении страницы
     было именно этим: место под фото уже занято, а картинка возникает
     мгновенно и рывком. */
  opacity:0;transition:opacity .32s ease;
}
.shots-track img.ready{opacity:1}

/* Заметно светлее, чем было: прежний вариант (#0c0c0e → #191920) на тёмном
   экране не отличался от чёрного, и человек видел просто провал вместо
   подсказки «картинка идёт». */
.shots{--shot-bg:linear-gradient(100deg,#1b1e22 26%,#333941 45%,#1b1e22 64%)}
[data-theme="light"] .shots{
  --shot-bg:linear-gradient(100deg,#edeff1 28%,#f6f7f8 45%,#edeff1 62%);
}

@keyframes shot-wave{to{background-position:-280% 0}}

@media (prefers-reduced-motion:reduce){
  .shot{animation:none}
  .shots-track img{transition:none;opacity:1}
}
.shots-dots{
  display:flex;justify-content:center;gap:6px;padding:10px 0 0;
}
.shots-dots i{
  width:5px;height:5px;border-radius:50%;background:var(--graphite);
  transition:background .2s,transform .2s;
}
.shots-dots i.on{background:var(--frost);transform:scale(1.25)}

/* ── Медаль: контур двойной толщины — вручается руками и должна отличаться
   от посчитанной ачивки ──────────────────────────────────────────────────── */
.holo{
  color:var(--acid-text)!important;
  border:2px solid var(--acid)!important;
  background:var(--void)!important;
  font-weight:600;
}
.holo i{color:var(--acid)!important}

/* ── Достижения: тонкий контур того же цвета ─────────────────────────────── */
.holo-text{
  color:var(--acid-text)!important;
  border:1px solid var(--acid)!important;
  background:var(--void)!important;
}
.holo-text i{color:var(--acid)!important}

/* ── Поставленный лайк ───────────────────────────────────────────────────── */
.fact.like.on{color:var(--acid)}
.fact.like.on span{color:var(--frost)}

/* ── Звёзды автора в ленте: плотный значок ───────────────────────────────── */
.fcard-stars{
  padding:2px 7px;border-radius:9px;
  background:var(--acid);color:var(--on-acid);
  font-weight:600;
}

/* ── Значок медали рядом с именем ────────────────────────────────────────── */
.name-medal{
  display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;border-radius:50%;flex:0 0 auto;
  background:var(--acid);
}

/* =============================================================================
   БЕГУЩАЯ СТРОКА СКИДОК
   Единственная реклама в кабинете, поэтому ведёт себя тихо: одна строка,
   без картинок и рамок. Цвет — тот же голографический, что у наград:
   в чёрном интерфейсе он читается как «здесь что-то приятное».
   ============================================================================= */
.offers{
  overflow:hidden;margin:22px 0;padding:10px 0;
  border-top:1px solid var(--hairline-dim);border-bottom:1px solid var(--hairline-dim);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
.offers-run{
  display:flex;align-items:center;gap:26px;width:max-content;
  animation:offers-run 40s linear infinite;
}
.offers:hover .offers-run{animation-play-state:paused}
@keyframes offers-run{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}
}

.offer{display:inline-flex;align-items:baseline;gap:8px;white-space:nowrap}
.offer-name{font-size:12px;color:var(--ash)}
/* Не цена, а приглашение: числу здесь верить нельзя, а ссылке можно */
.offer-go{font-size:11px;color:var(--acid-text);white-space:nowrap}
@media (prefers-reduced-motion:reduce){
  .offers-run{animation:none}
}

/* Вывод диагностики: моноширинный, с прокруткой, чтобы длинный отчёт
   не растягивал страницу */
.diag{
  margin:14px 0 0;padding:12px;max-height:50vh;overflow:auto;
  border:1px solid var(--hairline);border-radius:3px;
  font:400 11px/1.5 var(--f-mono);color:var(--ash);
  white-space:pre-wrap;word-break:break-word;background:var(--paper);
}

/* ── Комментарии ─────────────────────────────────────────────────────────── */
.cmt{padding:12px 0;position:relative}
.cmt-who{
  display:flex;align-items:center;gap:9px;background:none;border:none;padding:0;
  cursor:pointer;text-align:left;width:100%;
}
.cmt-who .ava{width:24px;height:24px}
.cmt-who .ava.letter{font-size:11px}
.cmt-body{margin:9px 0 0;color:var(--ash);white-space:pre-wrap;word-break:break-word}
.cmt-del{
  background:none;border:none;padding:6px 0 0;cursor:pointer;
  color:var(--graphite);font:400 11px/1 var(--f-body);letter-spacing:.08em;
  text-transform:uppercase;
}
.cmt-del:hover{color:var(--danger)}

/* Форма ввода: одна строка с кнопкой внутри, без разделителей.
   Ничего не объясняем текстом — поле и кнопка говорят сами за себя. */
.cmt-form{
  position:sticky;bottom:0;z-index:2;
  display:flex;flex-direction:column;gap:8px;
  margin-top:16px;padding:12px 0 4px;background:var(--void);
}
.cmt-input{
  display:flex;align-items:flex-end;gap:8px;
  border:1px solid var(--hairline);border-radius:22px;padding:6px 6px 6px 16px;
  transition:border-color .16s;
}
.cmt-input:focus-within{border-color:var(--frost)}
.cmt-form textarea{
  flex:1 1 auto;width:100%;background:none;border:none;
  color:var(--frost);font:400 15px/1.4 var(--f-body);padding:7px 0;resize:none;
  max-height:120px;
}
.cmt-form textarea:focus{outline:none}
.cmt-send{
  flex:0 0 auto;width:34px;height:34px;border-radius:50%;border:none;cursor:pointer;
  background:var(--frost);color:var(--void);font-size:15px;
  display:flex;align-items:center;justify-content:center;transition:opacity .16s;
}
.cmt-send:disabled{opacity:.3;cursor:default}

/* ── События ─────────────────────────────────────────────────────────────── */
.evt{
  display:flex;gap:11px;padding:13px 0;align-items:flex-start;
  border-bottom:1px solid var(--hairline-dim);cursor:pointer;
}
.evt .ava{width:28px;height:28px;flex:0 0 auto}
.evt-body{display:flex;flex-direction:column;gap:4px;min-width:0}
.evt-line{color:var(--mist);font-size:13px}
.evt-line b{color:var(--frost);font-weight:600}
.evt-quote{
  color:var(--ash);font-size:13px;
  border-left:1px solid var(--hairline);padding-left:10px;
}
/* Непрочитанное отмечено голографической точкой — тем же языком, что награды */
.evt.fresh{position:relative;padding-left:14px}
.evt.fresh::before{
  content:'';position:absolute;left:0;top:20px;width:5px;height:5px;border-radius:50%;
  background:var(--acid);
}

/* Кнопка «новые заточки»: не подменяем ленту под пальцами, а предлагаем */
.new-posts{
  display:flex;margin:0 auto 18px;
  background:var(--acid);border-color:transparent;color:var(--on-acid);
}
.new-posts:hover{color:var(--on-acid)}
/* Слои и просмотр фото тоже перестают быть fixed — иначе они накрывают
   только верхние 812 pt, и под ними остаётся видна лента */


/* Ответы: один уровень вложенности, отступ и черта слева */
.cmt.reply{margin-left:22px;padding-left:12px;border-left:1px solid var(--hairline)}
.cmt-acts{display:flex;gap:16px;margin-top:6px}
.cmt-link{
  background:none;border:none;padding:0;cursor:pointer;
  color:var(--graphite);font:400 11px/1 var(--f-body);
  letter-spacing:.08em;text-transform:uppercase;
}
.cmt-link:hover{color:var(--frost)}
.cmt-link.danger:hover{color:var(--danger)}
.reply-to{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  width:100%;padding:6px 14px;border-radius:14px;
  background:var(--raise);color:var(--mist);font-size:12px;
}
.reply-to b{color:var(--frost);font-weight:600}

/* =============================================================================
   ОБНОВЛЕНИЕ ЛЕНТЫ ПОТЯГИВАНИЕМ

   Механика снята с ленты Instagram покадрово. Раньше у нас вниз выезжала
   плашка-кружок поверх неподвижной ленты — знак того, что «что-то
   происходит». Там устроено иначе: вниз едет сама лента, а в открывшемся
   над ней разрыве стоит спиннер.

   Разница в том, кто отвечает пальцу. Плашка — посредник: палец тянет ленту,
   а двигается кружок, и связь между движением и результатом приходится
   достраивать в голове. Когда вниз едет лента, отвечает ровно то, за что
   тянут. Спиннер при этом в жесте не участвует: он неподвижно стоит на своём
   месте и просто проявляется из-под шапки — это не указатель натяжения,
   а обещание, что отпускать уже можно.

   Шапка не двигается. Замерено: логотип, «плюс» и «сердце» на записи стоят
   намертво, едет только содержимое под ними. У нас шапка position:fixed,
   поэтому это выходит само.

   Спиннер — системный айосовский: восемь скруглённых лучей по кругу
   с убывающей прозрачностью, вращение ступенями, а не плавное. Плавное
   вращение восьми одинаковых лучей читается как дрожание, а не как работа.
   Диаметр 24 px и центр в 29 px под шапкой — оба числа с записи.
   ============================================================================= */
#pull-spin{
  position:fixed;left:50%;z-index:45;
  /* 17 = 29 (центр по замеру) − 12 (половина диаметра) */
  top:calc(var(--topbar-h, 64px) + 17px);
  margin-left:-12px;width:24px;height:24px;
  opacity:0;pointer-events:none;
  /* Прозрачность, масштаб и плавность пишет feed.js: пока ведёт палец,
     переходов быть не должно — сглаживание здесь читается как задержка. */
}
/* Вращение живёт на вложенном слое, а не на самом #pull-spin: анимация
   transform перебила бы масштаб, который ставит палец, — у анимации
   приоритет выше инлайнового стиля. Два слоя, две независимые роли. */
#pull-spin span{
  position:absolute;inset:0;display:block;
  animation:pull-spin .8s steps(8,end) infinite;
}
#pull-spin i{
  position:absolute;left:50%;top:0;
  width:3px;height:7px;margin-left:-1.5px;
  border-radius:1.5px;background:var(--mist);
  /* Ось вращения — центр круга: 12 px от верхнего края луча */
  transform-origin:50% 12px;
}
@keyframes pull-spin{to{transform:rotate(360deg)}}

@media (prefers-reduced-motion:reduce){
  #pull-spin span{animation-duration:2.4s}
}

/* Лента едет вслед за пальцем. Слой композитора выделяем только на время
   жеста: постоянный will-change на контейнере всей ленты держал бы в памяти
   текстуру во весь экран без всякой пользы. */
.app-scroll.pulling{will-change:transform}

/* =============================================================================
   НАГРАДЫ ЗНАЧКАМИ

   Пятнадцать подписанных пилюль занимали пол-экрана и читались как список
   задач. Значок узнаётся с одного взгляда, а расшифровка ушла в бегущие
   строки под ними.
   ============================================================================= */
.badges{display:flex;flex-wrap:wrap;gap:16px;margin-bottom:14px}
/* Только иконка, без обводки: круг вокруг каждого значка на чёрном фоне
   читался как пустая кнопка, а не как знак отличия. */
.badge-award{
  flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  color:var(--acid);font-size:19px;line-height:1;
}
/* Медаль вручается руками — она ярче и чуть крупнее посчитанной ачивки */
.badge-award.medal{font-size:22px;filter:drop-shadow(0 0 6px var(--acid-dim))}

/* ── Две встречные бегущие строки со справкой ──────────────────────────────
   Встречное движение выбрано не для красоты: две одинаково едущие строки
   сливаются в одну полосу, и глаз перестаёт различать, где кончается первая.
   Разное направление разделяет их без единой линии. */
.ticker{
  overflow:hidden;margin:0 0 8px;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
}
.ticker-run{
  display:flex;align-items:center;gap:22px;width:max-content;
  animation:tick-left 46s linear infinite;
  will-change:transform;
}
.ticker.back .ticker-run{animation-name:tick-right}
.ticker:hover .ticker-run{animation-play-state:paused}

/* Оба состояния заданы явно. Без «from» браузер берёт текущее значение —
   а оно none, то есть не длина, и интерполировать не с чем: анимация
   числится запущенной, но ничего не двигается. Ровно это и произошло. */
@keyframes tick-left{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}
}
@keyframes tick-right{
  from{transform:translateX(-50%)}
  to{transform:translateX(0)}
}

.tick{
  display:inline-flex;align-items:center;gap:7px;white-space:nowrap;
  font-size:12px;color:var(--mist);
}
.tick i{color:var(--acid);font-size:13px}
.tick b{color:var(--frost);font-weight:600}
/* Вторая строка — про недостигнутое, поэтому тише */
.ticker.back .tick{opacity:.55}

@media (prefers-reduced-motion:reduce){
  .ticker-run{animation:none}
}

/* На широком экране снимок во всю ширину колонки выглядит плакатом:
   лента перестаёт читаться как лента. Ограничиваем колонку карточки —
   пропорции 3:4 сохраняются, просто кадр меньше. */
@media (min-width:768px){
  .fcard{max-width:460px;margin-left:auto;margin-right:auto}
  .shot{max-height:560px}
}

/* Последний тумблер в группе не подчёркиваем: черта вплотную к кнопке
   читается как её часть, а не как разделитель строк. */
#publish-row{border-bottom:none}
.toggle-row:last-of-type{border-bottom:none}

/* Снимок на широком экране — чуть крупнее, чем было: 460 читалось мелко,
   900 превращало ленту в плакат. */
@media (min-width:768px){
  /* Карточка узкой колонкой по центру, а подписи без боковых полей:
     на широком экране колонка и так узкая, и поля внутри неё съедали
     половину строки. */
  .fcard{max-width:560px}
  .fcard-top,
  .fcard-body,
  .fcard-acts,
  .fcard-bare{padding-left:0;padding-right:0}
  .shot{max-height:680px}

  /* Шапка растягивалась во всю ширину экрана, а её содержимое жалось
     к самым краям. Держим ту же колонку, что и у остального интерфейса. */
  .topbar{
    padding-left:max(var(--pad-x), calc((100vw - 900px) / 2));
    padding-right:max(var(--pad-x), calc((100vw - 900px) / 2));
  }
}

/* Выбор темы и языка: невыбранная кнопка не должна выглядеть отключённой.
   Раньше «Светлая» на тёмном фоне читалась как недоступная — контур был
   того же тона, что у выключенных элементов. */
.theme-pick .chip,
.lang-pick .chip{
  border-color:var(--hairline);
  color:var(--ash);
  opacity:1;
}
.theme-pick .chip:hover,
.lang-pick .chip:hover{border-color:var(--frost);color:var(--frost)}
.theme-pick .chip.on,
.lang-pick .chip.on{
  background:var(--frost);color:var(--void);border-color:var(--frost);
}

/* Знак марки рядом со словом. */
.brand-mark{
  height:1.15em;width:auto;vertical-align:-.18em;margin-right:.42em;
}
/* Знак марки в светлой теме перекрашивается в её акцент. Файл на диске
   кислотно-жёлтый — это цвет чёрной темы, и она берёт его как есть; второго
   png ради светлой не заводим, один источник правды.

   Рецепт выглядит дико, и по-другому нельзя: hue-rotate в CSS не поворот
   тона, а линейное приближение, и от жёлто-зелёного до оранжевого оно
   не доезжает — останавливается на золоте. Работает только связка «сначала
   в чёрное, потом заново покрасить»: brightness(0) гасит цвет, сохраняя
   альфу, остальное набирает нужный тон.

   Числа подобраны перебором с замером результата: на выходе #ff5e22 при
   цели #ff5e24 — расхождение в две единицы синего, глазом не различить. */
[data-theme="light"] .brand-mark{
  filter:brightness(0) saturate(100%) invert(46%) sepia(60%) saturate(3000%) hue-rotate(346deg);
}
.eyebrow .brand-mark{height:1.6em;vertical-align:-.4em}

/* Подпись под именем в ленте: место, рейтинг, число заточек — то же, что
   видно на странице заточника. Мелко и приглушённо, чтобы не спорить
   с самой работой. */
.fcard-sub{
  display:flex;align-items:center;gap:5px;
  font-size:11px;color:var(--graphite);white-space:nowrap;
}
.fcard-sub i{font-style:normal;opacity:.6}
.fcard-who{flex-direction:column;align-items:flex-start;gap:2px}


/* Зерно в кнопке выбора камня: приглушённее названия, но читаемо.
   Отступ обязателен — без него «Тестовый брусок100/80» слипается в кашу. */
.chip-grit{
  margin-left:7px;opacity:.65;font-size:11px;
  font-family:var(--f-mono);white-space:nowrap;
}
.chip.on .chip-grit{opacity:.8}

/* Личная норма расхода: спокойная строка, не предупреждение.
   «На исходе» остаётся отдельным стилем — там нужен акцент. */
.bottle-norm{
  margin-top:6px;font-size:12px;color:var(--mist);
}

/* Панель вкладок не участвует в проявлении экрана.
   Сама она неподвижна — замерено, — но на пустой ленте вкладка проступает
   через прозрачность, и рядом с ней это читается как сползание панели.
   Отдельный слой отвязывает её от перерисовки содержимого. */
.tabbar{will-change:transform;contain:layout paint}

/* Перетаскивание камней. Карточка под пальцем приподнимается — видно,
   что её ведут, а не просто подсветили. */
#stones-list .card.dragging{
  opacity:.9;
  transform:scale(1.02);
  box-shadow:var(--drag-shadow);
  border-color:var(--acid);
  position:relative;z-index:5;
}
#stones-list .card{transition:transform .12s ease, box-shadow .12s ease}
/* Пока тащим — текст не выделяется: иначе на десктопе выходит каша */
#stones-list:has(.dragging){user-select:none}

@media (prefers-reduced-motion:reduce){
  #stones-list .card{transition:none}
  #stones-list .card.dragging{transform:none}
}

/* Полоска сетов: прокручивается вбок, не переносится.
   Наборов может быть сколько угодно, и перенос ломал бы вёрстку. */
.set-bar{
  display:flex;gap:8px;overflow-x:auto;margin-bottom:20px;
  scrollbar-width:none;-webkit-overflow-scrolling:touch;
  padding-bottom:2px;
}
.set-bar::-webkit-scrollbar{display:none}
.set-bar .chip{flex:0 0 auto;white-space:nowrap}
.set-bar .set-manage{opacity:.7;padding-left:12px;padding-right:12px}


/* Кнопки перестановки: только иконки, без рамок. Две обведённые кнопки
   рядом с названием выглядели как элементы формы и спорили с карточкой. */
.ord-btns{display:flex;flex-direction:column;gap:0;flex:0 0 auto;margin-left:4px}
.ord-btns button{
  width:32px;height:24px;padding:0;
  background:none;border:none;
  color:var(--graphite);cursor:pointer;font-size:20px;line-height:1;
  display:flex;align-items:center;justify-content:center;
  transition:color .14s;
}
.ord-btns button:active{color:var(--acid)}

/* Заголовок раздела арсенала с кнопкой разворачивания */
.arsenal-head{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
}
.arsenal-toggle{
  display:flex;align-items:center;gap:8px;
  background:none;border:none;padding:4px 0;cursor:pointer;
  color:var(--mist);font-size:19px;line-height:1;
}
.arsenal-toggle:active{color:var(--acid)}
.arsenal-count{
  font-size:12px;color:var(--graphite);letter-spacing:0;
}

/* Камень в чужом арсенале — одной строкой. Разнесённые по краям название
   и зерно заставляли глаз метаться через весь экран. */
.ars-line{
  display:block;font-size:13px;line-height:1.5;color:var(--mist);
}
.ars-row:has(.ars-line){display:block}

/* Заголовок сета: воздух сверху, чтобы группа читалась как группа,
   а не как продолжение предыдущей. */
/* Название сета — светлое и заметное: это заголовок группы, и он должен
   выделяться среди камней, а не теряться среди них. Камни, наоборот,
   приглушены: их много, и каждый по отдельности не должен спорить
   с заголовком. */
.ars-set{
  margin:22px 0 10px;padding-top:4px;
  font:600 11px/1 var(--f-body);letter-spacing:.1em;
  text-transform:uppercase;color:var(--frost);
}
.ars-set:first-child{margin-top:6px}

/* Стрелка между камнями: она разделяет этапы, и по ней читается
   последовательность. Тусклая терялась в тексте, и цепочка выглядела
   сплошной строкой без структуры. */
.step-arrow{
  color:var(--acid);
  padding:0 3px;
  font-weight:600;
}


/* =============================================================================
   Статус заточника — уровень, полоса, промокод
   =============================================================================
   Полоса нарочно без числа. Процент превращает статус в задание, а задание
   выполняют минимально достаточным способом: увидев «осталось 3 из 10», человек
   пишет три коротких комментария, а не участвует в жизни ленты. Без числа
   полоса читается как «уже прилично» или «ещё в начале» — этого хватает,
   чтобы захотеть продвинуться, и не хватает, чтобы вычислить порог.

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

/* Уровень стоит на своей поверхности: это не абзац текста, а состояние
   человека в клубе, и у состояния должна быть рамка. */
.lvl{
  margin:22px 0 26px;padding:16px 16px 17px;
  background:var(--paper);border:1px solid var(--hairline-dim);border-radius:16px;
}

.lvl-head{ display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; }

.lvl-title{
  font-family:var(--f-display);
  font-size:1.05rem;
  letter-spacing:.02em;
  color:var(--frost);
}

/* Следующая ступень — тем же шрифтом и кеглем, но прозрачная: она про то,
   чего ещё нет. Приглушение делает её тенью текущего статуса, а не второй
   равноправной надписью. */
.lvl-next{
  font-family:var(--f-display);
  font-size:1.05rem;
  letter-spacing:.02em;
  color:var(--frost);
  opacity:.3;
}

.lvl-bar{
  height:4px;
  border-radius:3px;
  background:var(--hairline-dim);
  margin:12px 0 8px;
  overflow:hidden;
}
.lvl-bar i{
  display:block;
  height:100%;
  background:var(--acid);
  border-radius:3px;
  /* Медленно: полоса должна доехать уже после того, как человек увидел экран,
     иначе движение проходит мимо внимания и смысла в анимации нет */
  transition:width .6s cubic-bezier(.22,.61,.36,1);
}

.lvl-hint{ font-size:.82rem; color:var(--mist); }

/* ── Карточка промокода ──────────────────────────────────────────────────── */

.promo-card{
  margin-top:18px;
  padding:16px 18px;
  border:1px solid var(--acid-dim);
  border-radius:14px;
  background:var(--acid-dim);
}
/* Потраченный код не убираем: человек должен видеть, что он был и на что ушёл.
   Приглушаем до состояния «архив». */
.promo-card.is-used{
  border-color:var(--hairline-dim);
  background:transparent;
  opacity:.45;
}

.promo-head{
  font-size:.78rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--mist);
  margin-bottom:8px;
}

.promo-code{
  font-family:var(--f-mono);
  font-size:1.35rem;
  letter-spacing:.1em;
  color:var(--acid-text);
  margin:2px 0 14px;
  /* Код переписывают руками с телефона — даём выделить его тапом целиком */
  user-select:all;
}
.promo-card.is-used .promo-code{ color:var(--ash); }

/* Карточка промокода собрана по правилам набора: белая поверхность на
   кремовом холсте, волосяная рамка linen и оранжевая полоса у левого края
   вместо заливки. Заливка во всю карточку перестаёт быть акцентом и
   становится фоном — глазу больше нечего выделять. */
[data-theme="light"] .promo-card{
  background:var(--paper);
  border-color:#e4e5e1;
  box-shadow:inset 5px 0 0 var(--acid);
}
/* Отдельного цвета у кнопки «новые заточки» больше нет: акцент в кабинете
   один, и заливка берёт его же. Раньше здесь стоял свой оранжевый — потому
   что тогдашний акцент (persimmon) для заливки был слишком тёмным. */
[data-theme="light"] .promo-card.is-used{
  background:transparent;
  box-shadow:none;
}

.promo-actions{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:10px; }
.promo-actions .pill{ text-decoration:none; }

.promo-note{ font-size:.78rem; color:var(--mist); }


/* =============================================================================
   Плашка обновления PWA
   =============================================================================
   Установленное приложение живёт своей жизнью: новый sw.js скачивается сам,
   но ждёт, пока закроются все вкладки, — а PWA на телефоне не закрывают
   неделями. Без этой плашки человек сидит на версии месячной давности и
   не знает об этом.

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

.sw-update{
  position:fixed;
  left:50%;
  /* Формула та же, что у отступа контента: --tabbar-h измеряет app.js
     (плотность панели может меняться), 12px — её собственный отступ снизу,
     env(safe-area-inset-bottom) — зона домашнего индикатора. --nav-h здесь
     не годится: это фиксированные 60px, которые не знают ни про реальную
     высоту панели, ни про вырез, и плашка уезжала под навигацию. */
  bottom:calc(var(--tabbar-h, 64px) + 26px + env(safe-area-inset-bottom, 0px));
  /* Прячем тремя средствами, и это не перестраховка.
     Один transform не годится: сдвиг считается в процентах от высоты САМОЙ
     плашки, а лежит она выше низа экрана на высоту панели. 140% собственной
     высоты этот отступ не перекрывают, и плашка висит на виду постоянно.
     opacity убирает её независимо от высоты, visibility выводит из чтения
     скринридерами, pointer-events снимает перехват тапов по невидимому
     прямоугольнику над кнопками навигации. */
  transform:translate(-50%, 24px);
  opacity:0;
  visibility:hidden;
  pointer-events:none;
  z-index:9000;
  display:flex;
  align-items:center;
  gap:12px;
  max-width:min(520px, calc(100vw - 28px));
  padding:10px 12px 10px 18px;
  border:1px solid var(--hairline);
  border-radius:14px;
  background:var(--glass);
  backdrop-filter:blur(14px);
  -webkit-backdrop-filter:blur(14px);
  box-shadow:var(--bar-shadow);
  /* visibility без задержки погасила бы плашку до конца анимации ухода */
  transition:transform .38s cubic-bezier(.22,.61,.36,1),
             opacity .28s ease,
             visibility 0s linear .38s;
}
.sw-update.show{
  transform:translate(-50%, 0);
  opacity:1;
  visibility:visible;
  pointer-events:auto;
  transition:transform .45s cubic-bezier(.22,.61,.36,1),
             opacity .3s ease,
             visibility 0s;
}

/* Одной строкой и без переносов. Двухстрочный текст с подзаголовком давал
   высоту под 120px, и плашка начинала спорить с навигацией за место внизу
   экрана: любая неточность в отступе сразу становилась видна. Короткая
   строка держит её низкой, а «что делать» и так написано на кнопке. */
.sw-update-text{
  font-size:.86rem;
  color:var(--frost);
  line-height:1.2;
  white-space:nowrap;
}

.sw-update-actions{ display:flex; align-items:center; gap:6px; margin-left:auto; }


/* Титул уровня на чужой странице. Кислотный, как награды: это знак отличия,
   и разводить его с ними по цветам нельзя — читалось бы как другая система.
   Мелко и капслоком, чтобы стоять подписью под именем, а не спорить с ним. */
.person-level{
  margin:6px 0 2px;
  font-size:.72rem;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--acid-text);
}

/* Первое слово в заголовке входа. «Сообщество» длиннее «Клуба» на четыре
   буквы, и в одном кегле с «заточниками» строка перестаёт помещаться на
   узком экране. Уменьшение решает и композиционную задачу: главное здесь
   всё-таки «заточников», а «сообщество» — уточнение к нему. */
.login h1 .lead-word{
  display:block;
  font-size:.52em;
  /* Свой line-height обязателен: заголовок стоит на 0.96 от СВОЕГО кегля,
     и для вдвое меньшего слова этот интервал превращается в пустую полосу
     под ним. Отрицательный margin добирает остаток — надстрочная часть
     шрифта у заглавных букв всё равно не занята. */
  line-height:1;
  letter-spacing:.02em;
  color:var(--mist);
  margin-bottom:-.06em;
}
