:root {
  --bg: #ffffff;
  --fg: #17181a;
  --fg-dim: #707579;
  --card: #f4f4f6;
  --border: rgba(0, 0, 0, 0.10);
  --accent: #2e7cf6;
  --accent-fg: #ffffff;
  --link: #2e7cf6;
  --danger: #ff6b6b;
  --ok: #57c785;
  --safe-b: env(safe-area-inset-bottom, 0px);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* ── единый тактильный отклик для всех кликабельных элементов ───────────────────────────────
   transition общий, конкретные :active/:hover ниже задают силу эффекта под тип элемента. */
button, .home-tile, .project-link, .tile, a.project-link {
  transition: transform .12s ease, opacity .12s ease, box-shadow .12s ease, background-color .12s ease, filter .12s ease;
}

@media (hover: hover) {
  button:not(:disabled):hover,
  .home-tile:hover,
  .project-link:hover,
  .tile:hover {
    transform: scale(1.02);
  }

  /* карточки/нейтральные кнопки на фоне var(--card) — заметная акцентная подсветка */
  .home-tile:hover,
  .project-link:hover,
  .back-btn:hover,
  .balance-chip:hover,
  .home-balance-action:hover,
  .balance-action-btn:hover {
    background-color: color-mix(in srgb, var(--accent) 28%, var(--card));
  }

  /* уже акцентно-заполненные кнопки (сплошной var(--accent)) — становятся ярче/темнее того же цвета */
  .submit-btn:hover,
  .topup-btn:hover,
  .home-balance-action.primary:hover,
  .balance-action-btn.primary:hover {
    filter: brightness(1.15) saturate(1.1);
  }

  /* кнопка «Предложить идею» — прозрачная с обводкой, тоже получает лёгкую подсветку */
  .home-suggest-btn:hover {
    background-color: color-mix(in srgb, var(--accent) 16%, transparent);
    border-color: var(--accent);
  }

  .topup-partner-btn:hover {
    background-color: color-mix(in srgb, var(--accent) 16%, transparent);
    border-color: var(--accent);
  }
}

button:not(:disabled):active,
.home-tile:active,
.project-link:active,
.tile:active {
  transform: scale(0.96);
}

@keyframes bg-drift {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  background-image:
    radial-gradient(circle at 15% 8%, rgba(77, 163, 255, 0.10) 0%, transparent 45%),
    radial-gradient(circle at 85% 92%, rgba(77, 163, 255, 0.08) 0%, transparent 50%);
  background-size: 160% 160%;
  animation: bg-drift 22s ease-in-out infinite;
  color: var(--fg);
  font: 15px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

@supports (background: color-mix(in srgb, red 10%, transparent)) {
  body {
    background-image:
      radial-gradient(circle at 15% 8%, color-mix(in srgb, var(--accent) 10%, transparent) 0%, transparent 45%),
      radial-gradient(circle at 85% 92%, color-mix(in srgb, var(--accent) 8%, transparent) 0%, transparent 50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  body { animation: none; }
}

.topbar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 14px 16px 0;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 5;
}

/* шапка (баланс/статистика) занимает много места на телефоне — при открытой клавиатуре
   (фокус на текстовом поле формы) прячем её целиком, чтобы освободить экран под ввод */
/* Шапка при клавиатуре БОЛЬШЕ НЕ ПРЯЧЕТСЯ. Она закреплена сверху и занимает
   место в потоке; убирая её из потока в момент фокуса, страница мгновенно
   подскакивала на её высоту - человек касался поля, и экран уезжал у него
   из-под пальца.

   Задумка была освободить место под ввод, но клавиатура и так сжимает окно,
   а прыжок в момент касания пугает сильнее, чем помогают выигранные
   пятьдесят пикселей. В чате шапку и раньше не трогали - ровно по этой же
   причине. */

.balance-icon { font-size: 13px; line-height: 1; }

/* ── balance screen ──────────────────────────────────────────────────────────────────────── */
.balance-view { max-width: 480px; margin: 0 auto; padding: 4px 4px calc(24px + var(--safe-b)); }

.back-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  background: var(--card);
  color: var(--fg);
  font-size: 14.5px;
  font-weight: 600;
  font-family: inherit;
  padding: 9px 18px 9px 14px;
  border-radius: 999px;
  /* 14px здесь складывались с верхним полем экрана и отступом заголовка -
     втроём они съедали высоту карточки на КАЖДОМ экране приложения. */
  margin-bottom: 8px;
  cursor: pointer;
}

.back-btn:active {
  transform: scale(0.95);
  opacity: .85;
}

.balance-card {
  background: var(--card);
  border-radius: 16px;
  padding: 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.balance-card-row { display: flex; align-items: center; gap: 14px; min-width: 0; }

.balance-card-icon { font-size: 28px; line-height: 1; }

.balance-card-value { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; line-height: 1.2; }

.balance-card-label { font-size: 13px; color: var(--fg-dim); margin-top: 1px; }

.balance-action-btn {
  padding: 10px 16px;
  border-radius: 10px;
  border: none;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}

.balance-action-btn:active { opacity: .82; }

.balance-action-btn.secondary {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--accent);
  border: none;
}

/* ── topup modal ──────────────────────────────────────────────────────────────────────────── */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.55);
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.modal-card {
  width: 100%;
  max-width: 340px;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--bg);
  border-radius: 20px;
  position: relative;
  padding: 28px 20px 20px;
}

.modal-card .detail-close {
  background: var(--card);
  color: var(--fg-dim);
  width: 28px;
  height: 28px;
  font-size: 14px;
  top: 12px;
  right: 12px;
}

.modal-body { display: flex; flex-direction: column; gap: 12px; }

.topup-question { font-size: 16px; font-weight: 600; text-align: center; margin-bottom: 2px; padding: 0 50px; }

.topup-custom-input {
  width: 100%;
  padding: 13px 14px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--fg);
  font-size: 18px;
  font-weight: 600;
  text-align: center;
  font-family: inherit;
  outline: none;
}

.topup-custom-input:focus { border-color: var(--accent); }

.topup-partner-btn {
  width: 100%;
  padding: 13px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  font-size: 14.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.topup-partner-btn:active { opacity: .78; }

.feedback-textarea {
  width: 100%;
  padding: 13px 14px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--fg);
  font-size: 15px;
  font-family: inherit;
  line-height: 1.45;
  resize: none;
  outline: none;
}

.feedback-textarea:focus { border-color: var(--accent); }

.feedback-counter {
  font-size: 12px;
  color: var(--fg-dim);
  text-align: right;
  margin-top: -6px;
}

.feedback-success-text {
  font-size: 14px;
  color: var(--fg-dim);
  text-align: center;
  line-height: 1.5;
}

/* ── projects screen ──────────────────────────────────────────────────────────────────────── */
.projects-view { max-width: 480px; margin: 0 auto; padding: 4px 4px calc(24px + var(--safe-b)); }

.projects-heading {
  font-size: 14px;
  font-weight: 600;
  color: var(--fg-dim);
  margin: 0 4px 8px;
  letter-spacing: -0.1px;
}

.projects-list { display: flex; flex-direction: column; gap: 8px; }

.project-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
  padding: 22px 16px;
  border-radius: 14px;
  border: none;
  background: var(--card);
  text-decoration: none;
  color: var(--fg);
  text-align: center;
  font-family: inherit;
  cursor: pointer;
}

.project-link:active {
  opacity: .88;
  background: var(--accent);
  color: var(--accent-fg);
}

.project-link:active .project-link-desc {
  color: var(--accent-fg);
  opacity: .85;
}

.project-link-title { font-size: 17px; font-weight: 700; letter-spacing: -0.2px; }

.project-link-desc {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--fg-dim);
  line-height: 1.35;
}

/* Переливающийся градиент - фон плавно едет по цветам. Текст белый с лёгкой тенью,
   чтобы читался на любом участке градиента.

   Цвета приглушены (Кирилл 2026-08-27). Было розовый-фиолетовый-синий-бирюза-жёлтый:
   полная радуга на одной кнопке перетягивала внимание со всего экрана, а жёлтый участок
   вдобавок терял белый текст. Теперь четыре близких приглушённых тона - перелив
   чувствуется, но кнопка перестала быть самым ярким пятном приложения. */
.project-link-rainbow {
  background: linear-gradient(
    100deg,
    #3F6B78, #4A5A7C, #63577A, #47756B, #3F6B78
  );
  background-size: 300% 100%;
  animation: rainbow-slide 9s linear infinite;
}

.project-link-rainbow .project-link-title,
.project-link-rainbow .project-link-desc {
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .3);
}

@keyframes rainbow-slide {
  0% { background-position: 0% 50%; }
  100% { background-position: 300% 50%; }
}

/* Наведение/нажатие меняют фон у обычных карточек — здесь фон нужен свой, оставляем
   только масштаб и лёгкое затемнение. */
.project-link-rainbow:hover,
.project-link-rainbow:active {
  background: linear-gradient(
    100deg,
    #3F6B78, #4A5A7C, #63577A, #47756B, #3F6B78
  );
  background-size: 300% 100%;
}

.project-link-rainbow:active .project-link-desc { color: #fff; }

@media (prefers-reduced-motion: reduce) {
  .project-link-rainbow { animation: none; }
}

.projects-divider {
  height: 1px;
  background: var(--border);
  margin: 22px 4px;
}

/* ── news screen ──────────────────────────────────────────────────────────────────────────── */
.news-view { max-width: 480px; margin: 0 auto; padding: 4px 4px calc(24px + var(--safe-b)); }

.news-list { display: flex; flex-direction: column; gap: 12px; }

.news-card {
  padding: 16px 18px;
  border-radius: 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 4px 12px rgba(16, 24, 40, .05);
}

.news-card-title {
  font-size: 15.5px;
  font-weight: 700;
  line-height: 1.35;
  margin: 0 0 7px;
  letter-spacing: -0.25px;
}

.news-card-summary {
  font-size: 13px;
  color: var(--fg-dim);
  line-height: 1.45;
}

.news-updated {
  text-align: center;
  font-size: 12px;
  color: var(--fg-dim);
  margin-top: 16px;
}

/* ── earn points screen ───────────────────────────────────────────────────────────────────── */
.earn-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; }

.earn-card {
  padding: 16px 18px;
  border-radius: 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 4px 12px rgba(16, 24, 40, .05);
}

.earn-card-action {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid var(--border);
  color: var(--fg);
  font-family: inherit;
  cursor: pointer;
}

.earn-card-go {
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  margin-top: 10px;
}

.earn-card-title { font-size: 13px; font-weight: 600; color: var(--fg-dim); margin-bottom: 6px; letter-spacing: -0.1px; }
.earn-card-amount { font-size: 21px; font-weight: 700; color: var(--accent); margin-bottom: 6px; letter-spacing: -0.4px; }
.earn-card-desc { font-size: 13px; color: var(--fg-dim); line-height: 1.45; }

.earn-note {
  font-size: 13px;
  color: var(--fg-dim);
  text-align: center;
  line-height: 1.5;
  margin: 18px 8px 18px;
}

main { padding: 16px 16px calc(24px + var(--safe-b)); max-width: 560px; margin: 0 auto; }

.state-msg { text-align: center; color: var(--fg-dim); padding: 40px 16px; font-size: 14px; }

.hidden { display: none !important; }

.title { font-size: 19px; font-weight: 600; letter-spacing: -0.3px; margin: 2px 0 18px; }

.field { display: block; margin-bottom: 18px; }

.field-label {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-dim);
  margin-bottom: 7px;
}

.field-optional { font-weight: 400; font-size: 12px; opacity: .75; }

input[type="text"], input[type="search"], input[type="file"], select {
  width: 100%;
  max-height: 46px;
  padding: 11px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--fg);
  font-size: 16px;
  font-family: inherit;
  outline: none;
  transition: border-color .15s;
}

textarea {
  width: 100%;
  padding: 11px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--fg);
  font-size: 16px;
  font-family: inherit;
  line-height: 1.45;
  resize: none;
  outline: none;
  transition: border-color .15s;
}

input[type="text"]:focus, input[type="search"]:focus, select:focus, textarea:focus { border-color: var(--accent); }

input[type="file"] { font-size: 14px; padding: 9px 12px; }

.price-row { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 10px; }

.markets-box { display: flex; flex-wrap: wrap; gap: 8px; }

.market-chip {
  padding: 8px 13px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card);
  font-size: 14px;
  cursor: pointer;
  user-select: none;
  transition: background .12s, border-color .12s;
}

.market-chip.selected {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
  font-weight: 500;
}

.photo-preview-wrap { margin-top: 11px; }

.photo-preview {
  max-width: 100%;
  max-height: 260px;
  border-radius: 12px;
  display: block;
}

.hint { font-size: 12.5px; color: var(--fg-dim); margin-top: 7px; line-height: 1.4; }
.hint.error { color: var(--danger); }
.hint.ok { color: var(--ok); }

.submit-btn {
  width: 100%;
  padding: 14px;
  border-radius: 12px;
  border: none;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.2px;
  cursor: pointer;
  transition: opacity .15s;
}

.submit-btn:active { opacity: .82; }
.submit-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* Блик по кнопке. Нужен там, где действие платное и человек колеблется:
   кнопка должна выглядеть живой, а не ждать, пока её заметят.
   Блик идёт РЕДКО - раз в четыре секунды: непрерывное движение через минуту
   превращается в раздражитель, который перестают видеть. */
.btn-shine { position: relative; overflow: hidden; }
.btn-shine::after {
  content: ""; position: absolute; top: -60%; bottom: -60%; left: -40%; width: 32%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .42), transparent);
  transform: skewX(-18deg);
  animation: btnShine 4s ease-in-out infinite;
  pointer-events: none;
}
/* Пока кнопка недоступна, блик обещает то, чего нажатие не даст. */
.btn-shine:disabled::after { display: none; }
@keyframes btnShine {
  0%   { left: -40%; }
  55%  { left: 115%; }
  100% { left: 115%; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-shine::after { display: none; }
}

/* ── home screen ──────────────────────────────────────────────────────────────────────────── */
.home-view { max-width: 480px; margin: 0 auto; padding: 14px 16px calc(24px + var(--safe-b)); }

.home-balance-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 4px 10px;
}

.home-balance-item {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  border: none;
  background: none;
  padding: 4px 2px;
  color: var(--fg);
  font-family: inherit;
  cursor: pointer;
  border-radius: 8px;
}


.home-balance-action {
  padding: 7px 12px;
  border-radius: 999px;
  border: none;
  background: var(--card);
  color: var(--fg);
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  margin-right: auto;
}

/* Пары прижаты каждая к себе; между парами - обычный промежуток, а не растянутый. */
.home-balance-group {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

/* Баллы левее: пара с ними начинает строку, вторая пара уходит вправо. */
.home-balance-bar { justify-content: space-between; }
.home-balance-action { margin-right: 0; }
.home-balance-action:last-child { margin-right: 0; margin-left: 0; }

.home-balance-action.primary {
  background: var(--accent);
  color: var(--accent-fg);
}

.home-balance-action:active { opacity: .8; }

.home-stats-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px 7px;
  padding: 0 4px 12px;
  font-size: 11.5px;
  color: var(--fg-dim);
}

.home-stats-item { white-space: nowrap; }

.home-stats-clickable {
  border: none;
  background: none;
  padding: 0;
  font-size: inherit;
  color: inherit;
  font-family: inherit;
  cursor: pointer;
  border-bottom: 1px dashed var(--border);
}

.home-stats-divider { color: var(--border); font-size: 11px; }

.home-stats-item b { color: var(--fg); font-weight: 600; }

.earn-cta {
  display: block;
  width: 100%;
  padding: 10px;
  margin: 0 0 10px;
  border-radius: 12px;
  border: none;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 14px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  letter-spacing: -0.2px;
}

.home-quickbar {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 8px;
}

.home-quickbar-divider {
  height: 1px;
  background: var(--border);
  margin: 0 0 10px;
}

/* Быстрые кнопки: они вспомогательные и не должны занимать высоту наравне с плитками.
   ВАЖНО: padding отсюда перебивается правилом ниже по файлу - высоту менять ТАМ. */
.quick-btn {
  padding: 5px 6px;
  border-radius: 11px;
  border: none;
  background: var(--card);
  color: var(--fg);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}

.home-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.home-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  padding: 16px 14px;
  border-radius: 16px;
  border: none;
  background: var(--card);
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  min-height: 96px;
}

.home-tile:active { opacity: .82; }

.home-tile-icon { font-size: 24px; line-height: 1; }

.home-tile-title { font-size: 13.5px; font-weight: 500; line-height: 1.3; color: var(--fg); }

/* Цена в правом верхнем углу плитки. Человек должен видеть её ДО того, как
   откроет раздел и начнёт заполнять форму, - иначе цена всплывает уже после
   работы, и это читается как подвох. */
.tile-price {
  position: absolute; top: 8px; right: 8px; z-index: 3;
  font-size: 11.5px; font-weight: 700; color: var(--fg-dim);
  background: rgba(255, 255, 255, .78); border-radius: 999px; padding: 2px 8px;
  line-height: 1.5; white-space: nowrap;
}
.home-tile { position: relative; }

.admin-entry-btn {
  display: block;
  width: 100%;
  margin-top: 10px;
  padding: 11px;
  border-radius: 12px;
  border: 1px dashed var(--border);
  background: transparent;
  color: var(--fg-dim);
  font-size: 13px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
}

.admin-menu { display: flex; flex-direction: column; gap: 12px; }

/* Плитки админки по две в ряд. Каждая занимала полную ширину ради двух строк
   текста, и до нижних приходилось листать целый экран. Пояснение под числом
   в сетке прячем: в нём смысл только при первом знакомстве с разделом,
   а дальше оно просто съедает место. */
/* Три в ряд. Возможностей в админке много, и при двух колонках список уходил
   на три экрана - до нижних плиток человек просто не долистывал. */
.admin-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px;
}
.admin-grid .earn-card { padding: 10px 8px; min-height: 74px; }
.admin-grid .earn-card-title {
  font-size: 10.5px; line-height: 1.2; margin-bottom: 3px; letter-spacing: -.1px;
}
/* Число - главное на плитке, но в треть ширины длинная подпись вроде
   «579 человек» не влезает: переносим по словам, а не режем. */
.admin-grid .earn-card-amount {
  font-size: 13.5px; margin-bottom: 0; letter-spacing: -.2px;
  line-height: 1.2; word-break: break-word;
}
.admin-grid .earn-card-desc { display: none; }
/* Фоновый значок в узкой плитке занимает половину её ширины и мешает
   читать цифру. */
/* Значок в плитке админки виден - правила его размера и положения стоят
   в конце файла, там же, где остальная раскладка панелей. */

/* Цены - тоже квадратиками. Карточка во всю ширину ради названия и одного поля
   занимала полэкрана на услугу, и до нужной приходилось листать. */
/* Цены оставляем по две: там поле ввода и кнопка, в треть ширины они
   не помещаются. */
.price-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
.price-grid .price-row {
  flex-direction: column; gap: 8px; align-items: stretch;
  padding: 12px 11px; min-height: 0; margin-bottom: 0;
}
.price-grid .price-info b { font-size: 12.5px; line-height: 1.25; display: block; }
.price-grid .price-edit { display: flex; gap: 6px; }
.price-grid .price-input {
  flex: 1 1 auto; min-width: 0; text-align: center;
  font-size: 15px; font-weight: 700; padding: 8px 6px;
}
.price-grid .admin-approve-btn {
  flex: 0 0 auto; width: auto; padding: 8px 10px; font-size: 12px;
}
/* Пояснение под названием в сетке не помещается - оно и не нужно: услуг мало,
   и каждая понятна по имени. */
.price-grid .earn-card-desc, .price-grid .tile-line { display: none; }

/* Версия стоит на месте заголовка, справа от «Назад» - как заголовки
   на остальных экранах. */
/* Версия - обычный заголовок в общей строке с «Назад», как на остальных
   экранах. Отрицательный отступ, которым её раньше подтягивали, оставлял
   кнопку на отдельной строке ниже - строка сверху пустовала. */
#adminAppVersion { text-align: right; }

/* Кнопка «поделиться разделом» - сразу справа от названия, без рамки и фона.
   Рамка делала из неё отдельный элемент управления, спорящий с заголовком
   за внимание; здесь нужен именно значок при названии. */
.projects-heading[data-share] {
  display: flex; align-items: center; justify-content: flex-end; gap: 6px;
}
/* Селектор с двумя классами обязателен: .copy-btn задаёт рамку и фон, и одного
   .share-btn ему по весу не хватало - коробка оставалась на месте. */
.projects-heading .share-btn.copy-btn {
  width: 22px; height: 22px; flex: 0 0 auto; padding: 0; margin-right: -2px;
  border: none; background: none; box-shadow: none; border-radius: 0;
  /* Приглушён: это подсобный значок, а не действие, ради которого сюда зашли. */
  opacity: .38;
}
.projects-heading .share-btn.copy-btn:active { opacity: .7; }
.projects-heading .share-btn.copy-btn.is-done { opacity: 1; background: none; }
.projects-heading .share-btn .copy-ico { width: 14px; height: 14px; }

/* Тот же значок в панели экрана ссылок - правее кнопки «Новая кампания».
   Заголовка там нет, поэтому правила пишем отдельно, но вид один в один. */
.links-bar .share-btn.copy-btn {
  width: 22px; height: 22px; flex: 0 0 auto; padding: 0; margin-left: 2px;
  border: none; background: none; box-shadow: none; border-radius: 0;
  opacity: .38;
}
.links-bar .share-btn.copy-btn:active { opacity: .7; }
.links-bar .share-btn.copy-btn.is-done { opacity: 1; background: none; }
.links-bar .share-btn .copy-ico { width: 14px; height: 14px; }

/* Тот же значок в панели ленты товаров - правее «Загрузить товары». */
.screen-topbar .share-btn.copy-btn {
  width: 22px; height: 22px; flex: 0 0 auto; padding: 0; margin-left: 2px;
  border: none; background: none; box-shadow: none; border-radius: 0;
  opacity: .38;
}
.screen-topbar .share-btn.copy-btn:active { opacity: .7; }
.screen-topbar .share-btn.copy-btn.is-done { opacity: 1; background: none; }
.screen-topbar .share-btn .copy-ico { width: 14px; height: 14px; }

/* Строка «Назад + заголовок» на ленте: та же раскладка, что на остальных
   экранах, - кнопка слева, название справа. */
/* Строка «Назад + название». Плотная: у кнопки свой отступ снизу в 14px,
   у экрана свой сверху - вместе они съедали высоту целой карточки на пустом
   месте. Отступ кнопки снимаем, строке оставляем 8px. */
.feed-bar { display: flex; align-items: center; gap: 8px; }
/* Название прижато к ПРАВОМУ краю, как заголовки на всех остальных экранах.
   Кнопка «Назад» слева, название справа - глаз ищет его там же, где всегда.
   Правило с двумя классами и признаком: иначе его перебивают более общие,
   и заголовок сползает влево, к самой кнопке. */
/* Заголовок прижимаем к правому краю САМИМ ЭЛЕМЕНТОМ - авто-отступом слева,
   а не выравниванием содержимого внутри него.
   Прошлая попытка выравнивала контент внутри растянутого блока, и её перебивали
   более общие правила: блок оставался во всю ширину, а текст вставал слева -
   заголовок выглядел приклеенным к «Назад». Авто-отступ перебить нечем:
   он двигает сам блок, и тот прижимается к краю при любой раскладке. */

/* Подзаголовки разделов ВНУТРИ экрана - слева и без прижатия к правому краю:
   заголовок экрана стоит справа от «Назад», а эти относятся к спискам под ними. */
h3.projects-heading { text-align: left; justify-content: flex-start; margin-top: 4px; }
h3.projects-heading[data-share] .share-btn { margin-left: 2px; }

/* Карточка генерации: фото, что определили, чья, и оценка. */
.gen-card { margin-bottom: 10px; }
.gen-head { display: flex; align-items: center; gap: 10px; }
.gen-photo {
  width: 48px; height: 48px; flex: 0 0 auto; border-radius: 10px;
  object-fit: cover; background: var(--bg-page, #f5f5f7);
}
.gen-meta { flex: 1 1 auto; min-width: 0; }
.gen-title { font-size: 14px; font-weight: 700; color: var(--fg); word-break: break-word; }
.gen-sub { font-size: 12px; color: var(--fg-dim); margin-top: 2px; }
.gen-mark { flex: 0 0 auto; font-size: 20px; }
/* Отзыв - главное на карточке: ради него сюда и заходят. */
.gen-review {
  margin-top: 8px; padding: 8px 10px; border-radius: 10px;
  background: #fef2f2; color: #991b1b; font-size: 13px; line-height: 1.4;
}
/* Само описание - справочно, поэтому мельче и приглушено. */
/* Описание показываем ЦЕЛИКОМ. Обрезка по высоте была нужна, пока карточки
   не сворачивались: теперь раскрытая карточка и открывается ради того, чтобы
   прочесть текст, а обрезанный конец гнал бы искать его в другом месте. */
.gen-desc {
  margin-top: 8px; font-size: 12.5px; line-height: 1.45; color: var(--fg-dim);
  white-space: pre-line;
}

.gen-who-link { color: var(--accent); text-decoration: none; font-weight: 600; }
.gen-who-link:active { opacity: .7; }

/* Ключевые слова - плитками: список через запятую в строку читается как
   продолжение описания, а это отдельная часть работы, за которую платят. */
.gen-keywords { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.gen-kw {
  font-size: 11.5px; line-height: 1.6; padding: 1px 8px;
  border-radius: 999px; background: var(--card); color: var(--fg-dim);
}

.admin-card-actions { display: flex; gap: 8px; margin-top: 12px; }

.admin-approve-btn,
.admin-reject-btn {
  flex: 1;
  padding: 10px;
  border-radius: 10px;
  border: none;
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.admin-approve-btn { background: var(--ok); color: #fff; }
.admin-reject-btn { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); }
.admin-approve-btn:disabled, .admin-reject-btn:disabled { opacity: .5; cursor: not-allowed; }

.admin-field { font-size: 13.5px; line-height: 1.6; }
.admin-field b { font-weight: 600; }
.admin-link { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }

.home-suggest-btn {
  display: block;
  width: 100%;
  margin-top: 22px;
  padding: 13px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-dim);
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
}

.home-suggest-btn:active { opacity: .7; }

/* Лампочка плавно разгорается и затухает — ровное свечение, без резких вспышек. */
.bulb-blink {
  display: inline-block;
  animation: bulb-blink 1.6s ease-in-out infinite;
}

@keyframes bulb-blink {
  0%, 78% { opacity: 1; }
  88% { opacity: 0; }
  98%, 100% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .bulb-blink { animation: none; }
}

.feed-view { max-width: 560px; margin: 0 auto; padding: 12px 12px calc(24px + var(--safe-b)); }

/* Лупа в шапке: прозрачный круг между кнопкой действия и значком «поделиться».
   Заливка появляется только когда поиск открыт - так видно, что раздел сейчас
   показывает не всё, а найденное (Кирилл 2026-09-18). */
.topbar-search-btn {
  width: 38px; height: 38px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 50%;
  /* Стекло лупы объёмное, значит и оправа под ней не плоская: свет сверху,
     тень снизу, тонкий внутренний блик по краю круга. */
  background: linear-gradient(160deg, rgba(255,255,255,.22), rgba(0,0,0,.05));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.55), 0 1px 2px rgba(0,0,0,.10);
  color: var(--fg); cursor: pointer; padding: 0;
  transition: background .18s ease, color .18s ease, transform .12s ease,
              box-shadow .18s ease, border-color .18s ease;
}
.topbar-search-btn:active { transform: scale(.9); }
.topbar-search-btn.is-on {
  background: var(--accent); border-color: transparent; color: #fff;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35), 0 2px 8px rgba(46,124,246,.35);
}
.lupa { width: 21px; height: 21px; display: block; }

/* Поле выезжает сверху вниз, а не появляется рывком: раскрытие видно глазом,
   и человек понимает, откуда взялась строка. */
.search-row {
  position: relative;
  display: flex; align-items: center; gap: 8px;
  max-height: 0; opacity: 0; overflow: hidden;
  margin: 0; padding: 0 12px;
  border-radius: 14px; background: var(--card); border: 1px solid transparent;
  transform: translateY(-8px);
  transition: max-height .28s cubic-bezier(.2, .8, .3, 1), opacity .2s ease,
              transform .28s cubic-bezier(.2, .8, .3, 1), margin .28s ease,
              border-color .2s ease;
}
.search-row.is-open {
  max-height: 52px; opacity: 1; transform: translateY(0);
  margin: 0 0 12px; border-color: var(--border);
}
/* Бегущая подсказка лежит ПОД полем, а не в нём: настоящий placeholder нельзя
   ни анимировать, ни показать с курсором. Гаснет, как только появился текст. */
.search-ghost {
  position: absolute; left: 14px; right: 44px;
  color: var(--fg-dim); font-size: 15px; line-height: 46px;
  pointer-events: none; white-space: nowrap; overflow: hidden;
}
.search-ghost::after {
  content: ""; display: inline-block; width: 1.5px; height: 1em;
  margin-left: 2px; vertical-align: -.16em; background: var(--fg-dim);
  animation: ghostCaret 1s steps(1) infinite;
}
.search-ghost.is-off { display: none; }
/* В поле уже стоит настоящий курсор - свой рисовать не надо, иначе их два
   и оба мигают вразнобой. */
.search-ghost.is-static::after { display: none; }
@keyframes ghostCaret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
/* Через .search-row: общее правило для input[type=search] весит больше одного
   класса и возвращало рамку, а на фокусе красило её синим. Получалась коробка
   в коробке - серая полоса, а в ней синий прямоугольник (Кирилл 2026-09-18). */
.search-row .search-input,
.search-row .search-input:focus {
  flex: 1 1 auto; min-width: 0; height: 46px; padding: 0 2px;
  border: none; border-radius: 0; background: transparent;
  outline: none; box-shadow: none;
  font-family: inherit; font-size: 15px; color: var(--fg);
}
.search-input::-webkit-search-cancel-button { display: none; }
.search-clear {
  flex: 0 0 auto; width: 26px; height: 26px; border: none; padding: 0;
  border-radius: 50%; background: var(--border); color: var(--fg-dim);
  font-size: 13px; line-height: 1; cursor: pointer;
}
.search-clear.hidden { display: none; }

/* Найденные карточки проявляются, а не подменяются мгновенно: подмена выглядит
   как сбой, особенно когда от строки к строке меняется всего одна плитка. */
.grid-fade-in > .tile { animation: gridFadeIn .26s ease-out backwards; }
.grid-fade-in > .tile:nth-child(2) { animation-delay: .03s; }
.grid-fade-in > .tile:nth-child(3) { animation-delay: .06s; }
.grid-fade-in > .tile:nth-child(4) { animation-delay: .09s; }
.grid-fade-in > .tile:nth-child(n+5) { animation-delay: .12s; }
@keyframes gridFadeIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .search-row { transition: none; }
  .search-ghost::after { animation: none; }
  .grid-fade-in > .tile { animation: none; }
}

.filter-bar { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 12px; }

.filter-bar select {
  flex: 1;
  padding: 9px 10px;
  border-radius: 10px;
  font-size: 14px;
}

.feed-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* Барабан счётчика: старое число уезжает вверх, новое приходит снизу. Обёртка
   режет всё, что вышло за строку, поэтому цифра не наезжает на значок глаза. */
.count-roll { display: inline-block; overflow: hidden; vertical-align: -.15em; line-height: 1.15; }
.count-roll-val { display: inline-block; will-change: transform; }
.count-roll-val.is-out { animation: countRollOut .22s ease-in forwards; }
.count-roll-val.is-in { animation: countRollIn .34s cubic-bezier(.2, .8, .3, 1); }
@keyframes countRollOut {
  to { transform: translateY(-110%); opacity: 0; }
}
@keyframes countRollIn {
  from { transform: translateY(110%); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .count-roll-val.is-out, .count-roll-val.is-in { animation: none; opacity: 1; transform: none; }
}

/* Анкета поставщика: неполная форма приглушает кнопку, но не выключает её -
   нажатие объясняет, чего не хватает. Незаполненное поле подсвечивается и
   коротко вздрагивает, чтобы его было видно после прокрутки (Кирилл 2026-09-21). */
#supplierSubmitBtn.is-incomplete { opacity: .6; }
.field-missing {
  border-color: #d93a3a !important;
  box-shadow: 0 0 0 3px rgba(217, 58, 58, .16) !important;
  animation: fieldNudge .36s ease;
}
@keyframes fieldNudge {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}
@media (prefers-reduced-motion: reduce) { .field-missing { animation: none; } }

/* Запертая плитка профиля - серая и неживая: видно, что раздел есть, но он не про
   тебя. Нажатие не игнорируем - оно спрашивает, регистрироваться ли поставщиком. */
.home-tile.is-locked { filter: grayscale(1); opacity: .55; }
.home-tile.is-locked .home-tile-sub { color: var(--fg-dim); }

/* Номера страниц под лентой - по центру: ряд из трёх-четырёх кнопок, прижатый
   к левому краю, читается как начало списка, а не как его конец. */
.pager-box { display: flex; justify-content: center; padding-bottom: 6px; }
.pager-box.hidden { display: none; }
.pager-box .pager-row { justify-content: center; }

.tile {
  background: var(--card);
  border-radius: 14px;
  overflow: hidden;
  cursor: pointer;
  display: flex;
  flex-direction: column;
}

/* 2026-08-22 (Kirill): убрана дымка по возрасту — статус не флипается по TTL (см. feed_db.py),
   заявка полностью активна и старше суток; тусклость только вводила в заблуждение и роняла
   контраст фото. Дата-бейдж (aged-badge ниже) остаётся приглушённым как нейтральная метка. */

.tile-photo-wrap { position: relative; aspect-ratio: 1 / 1; background: var(--bg); }

.tile-photo { width: 100%; height: 100%; object-fit: cover; display: block; }

.tile-badge {
  position: absolute;
  top: 7px;
  left: 7px;
  font-size: 11px;
  font-weight: 500;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(0,0,0,.6);
  color: #fff;
  backdrop-filter: blur(6px);
}

.tile-badge.aged-badge { background: rgba(60,60,60,.7); }

.tile-badge-offers { top: auto; bottom: 7px; left: auto; right: 7px; background: rgba(45,125,246,.9); }

.tile-body { padding: 9px 10px 11px; }

.tile-title {
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.32;
  margin: 0 0 5px;
  letter-spacing: -0.1px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tile-line { font-size: 12px; color: var(--fg-dim); margin: 2px 0; }
.tile-price-line { font-size: 13px; font-weight: 600; color: var(--fg); margin-top: 4px; }

.detail-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.55);
  z-index: 20;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.detail-card {
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--bg);
  border-radius: 18px 18px 0 0;
  position: relative;
  padding-bottom: var(--safe-b);
}

/* Квадрат задан САМОЙ рамке, а не фотографии внутри неё. Пока фото грузится,
   у картинки нет размеров, рамка схлопывается, и стрелки переключения - они
   стоят по середине рамки - оказываются наверху; когда фото доезжает, рамка
   вырастает, и стрелки прыгают на своё место. С высотой у рамки прыгать нечему:
   она известна до загрузки. */
.detail-photo-wrap { position: relative; aspect-ratio: 1 / 1; overflow: hidden; background: var(--card); }

.detail-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: rgba(0,0,0,.5);
  color: #fff;
  font-size: 16px;
  cursor: pointer;
  z-index: 2;
  backdrop-filter: blur(6px);
}

/* крупнее и контрастнее именно поверх фото товара — на однотонном фоне модалок (пополнение,
   фидбек) размер выше не нужен, там кнопка и так хорошо видна */
.detail-overlay .detail-close {
  width: 40px;
  height: 40px;
  font-size: 19px;
  background: rgba(0,0,0,.65);
  box-shadow: 0 2px 8px rgba(0,0,0,.25);
}

.detail-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(0,0,0,.5);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
}

.detail-nav:active { opacity: .8; }
.detail-nav:disabled { opacity: .25; cursor: not-allowed; }

/* Та же болезнь, что была у затвора камеры: общее правило для всех кнопок
   в начале файла ставит transform: scale() при наведении и нажатии - и
   затирает translateY(-50%), которым стрелка отцентрована по высоте фото.
   При каждом нажатии стрелка прыгала вниз на двадцать пикселей и
   возвращалась. Центрирование и масштаб пишем вместе, одним свойством. */
.detail-nav:not(:disabled):hover { transform: translateY(-50%) scale(1.02); }
.detail-nav:not(:disabled):active { transform: translateY(-50%) scale(0.94); }
/* Пока стрелка недоступна, общее правило её и не трогает - но и мы не должны
   оставлять её без центрирования. */
.detail-nav:disabled { transform: translateY(-50%); }

.detail-nav-prev { left: 10px; }
.detail-nav-next { right: 10px; }

.detail-photo { width: 100%; height: 100%; object-fit: cover; display: block; }

.detail-body { padding: 16px; }
.detail-title { font-size: 17px; font-weight: 600; letter-spacing: -0.2px; margin: 0 0 12px; }
.detail-title-divider { height: 1px; background: var(--border); margin: 0 0 12px; }

/* Заголовок заявки занимает ровно две строки, сколько бы слов в нём ни было.
   При листании стрелками короткое название сменялось длинным, высота менялась,
   и всё под ним - фото, метки, текст - прыгало вверх-вниз. Скролл догонял это
   рывками. Фиксированная высота убирает сам сдвиг, а не его последствия
   (Кирилл 2026-09-18). Две строки - столько же, сколько у плитки в ленте. */
#detailTitle {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.3;
  height: 2.6em;
}
.detail-meta { font-size: 14px; color: var(--fg-dim); line-height: 1.65; margin-bottom: 11px; }
.detail-status { font-size: 13px; font-weight: 500; }
/* Срок и счётчики в одной строке по краям: слева сколько времени осталось,
   справа сколько людей уже посмотрело и ответило. */
.detail-status-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 12px;
}
.detail-counts { font-size: 12.5px; color: var(--fg-dim); white-space: nowrap; }
.detail-status.is-fresh { color: var(--ok); }
.detail-status.is-aged { color: var(--fg-dim); }
.detail-action { display: block; text-align: center; text-decoration: none; }

/* ── offers list (детальная карточка заявки) ─────────────────────────────────────────────── */
.detail-offers { margin-bottom: 15px; }

.detail-offers-heading { font-size: 13px; font-weight: 600; color: var(--fg-dim); margin-bottom: 10px; text-align: center; }

.offer-card {
  display: flex;
  gap: 10px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

.offer-card:last-child { border-bottom: none; }

.offer-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}

.offer-info { min-width: 0; flex: 1; }

.offer-supplier-label { font-size: 12px; color: var(--fg-dim); margin-bottom: 2px; }

.offer-price-qty { font-size: 14.5px; font-weight: 600; margin-bottom: 4px; }

.offer-comment { font-size: 13px; color: var(--fg-dim); line-height: 1.4; margin-bottom: 8px; }

.quick-avatar {
  display: inline-block;
  position: relative;
  width: 18px;
  height: 18px;
  vertical-align: -4px;
  margin-right: 2px;
}

.quick-avatar-img {
  width: 18px;
  height: 18px;
  border-radius: 4px;
  object-fit: cover;
  display: block;
}

.quick-avatar-icon {
  display: block;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  background: #17181a;
}

.my-request-wrap { display: flex; flex-direction: column; gap: 6px; }

.my-request-del {
  padding: 8px;
  border-radius: 10px;
  border: none;
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.my-request-del:disabled { opacity: .5; cursor: not-allowed; }

.name-field-wrap { position: relative; }

.name-field-wrap .profile-name-input { padding-right: 44px; }

/* Отцентрована трансформом - значит нуждается в той же защите от общего
   правила масштабирования кнопок, что стрелки и затвор. */
.name-save-check:not(:disabled):hover { transform: translateY(-50%) scale(1.02); }
.name-save-check:not(:disabled):active { transform: translateY(-50%) scale(0.94); }
.name-save-check {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  border-radius: 9px;
  border: none;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 15px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  line-height: 1;
}

.name-save-check:disabled { opacity: .55; cursor: default; }
.name-save-check.saved { background: var(--ok); }

.crop-actions { display: flex; gap: 8px; }
.crop-actions .topup-partner-btn { flex: 0 0 40%; margin: 0; }
.crop-actions .submit-btn { flex: 1; margin: 0; }

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

   touch-action: none здесь БЫЛО и ломало ползунок увеличения: Safari отдаёт перетаскивание
   бегунка <input type="range"> как обычный жест прокрутки, и запрет на всём body убивал его
   вместе с прокруткой — ползунок просто не двигался, увеличить фото было нечем. Прокрутку
   держит overflow: hidden, а жесты внутри кадра гасит сам кадр (.crop-viewport) и
   preventDefault в его обработчиках. */
body.crop-open { overflow: hidden; }

.crop-viewport {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 14px;
  background: #000;
  touch-action: none;
}

.crop-image {
  position: absolute;
  transform-origin: 0 0;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

.crop-frame {
  position: absolute;
  inset: 0;
  border: 2px solid rgba(255,255,255,.9);
  border-radius: 14px;
  pointer-events: none;
  box-shadow: 0 0 0 9999px rgba(0,0,0,.28);
}

/* Ползунок увеличения — крупный и с собственным touch-action, чтобы палец попадал в бегунок
   с первого раза, а не таскал вместо него страницу. */
.crop-zoom {
  width: 100%;
  height: 34px;
  margin: 0;
  accent-color: var(--accent);
  touch-action: manipulation;
}

.crop-tip {
  margin: 10px 0 2px;
  font-size: 12.5px;
  color: var(--fg-dim);
  text-align: center;
}

.profile-head {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 14px;
  margin-bottom: 12px;
}

.profile-head-fields {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.profile-avatar-wrap {
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
}

.profile-avatar {
  display: block;
  width: 84px;
  height: 84px;
  border-radius: 16px;
  object-fit: cover;
  background: var(--card);
}

.profile-avatar-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  border: 1px dashed var(--border);
}

.profile-name-input {
  width: 100%;
  padding: 11px 12px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--fg);
  font-size: 15px;
  font-weight: 600;
  font-family: inherit;
  outline: none;
}

.profile-name-input:focus { border-color: var(--accent); }

.points-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 14px;
  border-radius: 12px;
  background: var(--bg);
  border: 1px solid var(--border);
}

.points-main { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.points-amount { font-weight: 700; font-size: 15px; white-space: nowrap; }
.points-amount.plus { color: var(--ok); }
.points-amount.minus { color: var(--danger); }
.points-reason { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.points-source { font-size: 12.5px; font-weight: 600; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.points-date { font-size: 11.5px; color: var(--fg-dim); }

.points-pager {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  margin-top: 12px;
}

.points-page-btn {
  min-width: 34px;
  padding: 7px 10px;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.points-page-btn.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }

.broadcast-subscribe-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}

.offer-photo-wrap { position: relative; width: 100%; max-width: 220px; margin: 8px 0; }

.offer-photo {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 12px;
}

/* «Открыть контакт» лежит на фотографии, снизу по центру: отдельной строкой
   кнопка забирала ряд у каждого предложения, а их под заявкой бывает с десяток.

   Тень снизу - не украшение: кнопка ложится на чужую фотографию, а там внизу
   может оказаться что угодно, от белой коробки до белого пола. Без затемнения
   белая кнопка на белом фоне пропадает. */
.offer-photo-wrap::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 42%;
  border-radius: 0 0 12px 12px; pointer-events: none;
  background: linear-gradient(to top, rgba(0,0,0,.42), transparent);
}
.offer-action-btn.offer-action-on-photo {
  position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%);
  z-index: 2; white-space: nowrap; max-width: calc(100% - 16px);
  box-shadow: 0 2px 8px rgba(0,0,0,.28);
}
/* И здесь то же: без этих двух строк кнопка на фотографии уезжала бы вправо
   при каждом нажатии. */
.offer-action-btn.offer-action-on-photo:not(:disabled):hover { transform: translateX(-50%) scale(1.02); }
.offer-action-btn.offer-action-on-photo:not(:disabled):active { transform: translateX(-50%) scale(0.94); }
.offer-action-btn.offer-action-on-photo:disabled { transform: translateX(-50%); }

.respond-camera-wrap { position: relative; }

.respond-video {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 14px;
  background: #000;
}

.respond-shutter {
  position: absolute;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  width: 62px;
  height: 62px;
  border-radius: 50%;
  border: 4px solid rgba(255,255,255,.85);
  background: rgba(255,255,255,.35);
  cursor: pointer;
  backdrop-filter: blur(4px);
}

.respond-shutter:active { background: rgba(255,255,255,.6); }

/* Затвор ПЕРЕСТАЁТ убегать от пальца. Общее правило для всех кнопок в начале
   файла ставит им transform: scale() при наведении и нажатии - и затирает
   translateX(-50%), которым затвор отцентрован. Кнопка прыгала вправо на
   половину своей ширины, а на отпускании возвращалась: ровно то самое
   «дёргается и убегает от мышки».

   Здесь центрирование и масштаб записаны вместе, одним transform - тогда
   затирать нечего. Тот же приём уже стоял у кнопки «переснять» ниже. */
.respond-shutter:not(:disabled):hover { transform: translateX(-50%) scale(1.02); }
.respond-shutter:not(:disabled):active { transform: translateX(-50%) scale(0.94); }

/* Крестик выхода из видоискателя. Тёмный, в правом верхнем углу - там же,
   где закрывается всё остальное в приложении. */
.camera-close {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  width: 32px; height: 32px; border-radius: 50%;
  border: none; background: rgba(0,0,0,.5); color: #fff;
  font-size: 15px; line-height: 1; cursor: pointer; font-family: inherit;
  display: flex; align-items: center; justify-content: center;
  backdrop-filter: blur(4px);
}

.offer-action-btn {
  padding: 8px 14px;
  border-radius: 999px;
  border: none;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.offer-action-btn:disabled { opacity: .5; cursor: not-allowed; }

.offer-action-btn.is-subscribed { background: var(--ok); color: #fff; }

.offer-contact {
  font-size: 13px;
  color: var(--ok);
  line-height: 1.6;
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  border-radius: 10px;
  padding: 8px 10px;
}

.offer-hint { font-size: 12px; color: var(--danger); margin-top: 6px; }

.offer-confirm {
  padding: 10px 12px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 10%, var(--card));
}

.offer-confirm-text { font-size: 13px; font-weight: 600; margin-bottom: 8px; }

.offer-confirm-btns { display: flex; gap: 8px; }

.offer-cancel-btn {
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-dim);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}


/* Открытые контакты: фото тянется через Telegram-прокси и приезжает не мгновенно —
   вместо белой дыры показываем «мерцающий» плейсхолдер того же размера. */
.unlock-photo-wrap {
  width: 100%;
  max-width: 220px;
  aspect-ratio: 1 / 1;
  margin: 8px 0 0;
  border-radius: 12px;
  overflow: hidden;
  background: linear-gradient(90deg, #eef0f3 25%, #e2e5ea 37%, #eef0f3 63%);
  background-size: 400% 100%;
  animation: unlock-shimmer 1.3s ease-in-out infinite;
}

.unlock-photo-wrap.loaded {
  animation: none;
  background: none;
}

.unlock-photo-wrap .unlock-photo {
  width: 100%;
  height: 100%;
  max-width: none;
  margin: 0;
  opacity: 0;
  transition: opacity 0.18s ease;
}

.unlock-photo-wrap.loaded .unlock-photo { opacity: 1; }

@keyframes unlock-shimmer {
  0% { background-position: 100% 0; }
  100% { background-position: 0 0; }
}

.unlock-del { margin-top: 10px; width: 100%; }

/* Технические данные в карточке модерации — свёрнуты: нужны в одном случае из ста. */
.admin-tech { margin-top: 10px; }

.admin-tech summary {
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted, #8a8f98);
  padding: 6px 0;
  list-style: none;
}

.admin-tech summary::-webkit-details-marker { display: none; }
.admin-tech summary::before { content: "▸ "; }
.admin-tech[open] summary::before { content: "▾ "; }
.admin-tech .admin-field { padding-top: 4px; word-break: break-word; }

/* Строки списков в админке — компактнее обычных карточек, их бывает много. */
.admin-row { text-align: left; }
.admin-row .earn-card-title { font-size: 14.5px; }
.admin-row .earn-card-desc { margin-top: 2px; }

/* Аватарка в админских списках и карточке. У большинства пользователей её нет — остаётся
   чёрный круг с первой буквой имени, чтобы список не выглядел рваным. */
.admin-avatar {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #111;
  color: #fff;
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.admin-avatar-big { width: 56px; height: 56px; font-size: 22px; }

/* Фото лежит поверх буквы и проявляется, только когда действительно загрузилось. Так не
   бывает ни пустого круга на время загрузки, ни буквы поверх лица. */
.admin-avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity .18s ease;
}

.admin-avatar.has-photo .admin-avatar-img { opacity: 1; }
.admin-avatar.has-photo .admin-avatar-letter { visibility: hidden; }

.admin-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
}

.admin-row-text { min-width: 0; flex: 1; }

/* Строка списка — РОВНО две строки текста, ни больше. Раньше длинный хвост («поставщик:
   одобрен») переносился на третью строку у одних людей и не переносился у других, и список
   выглядел рваным: карточки разной высоты, аватарки на разных уровнях. Хвост, который не
   поместился, всё равно виден целиком в раскрытой карточке. */
.admin-row-text .earn-card-title,
.admin-row-text .earn-card-desc {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-person-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}

.admin-person-name { font-size: 17px; font-weight: 600; }

/* Поиск в админских списках — во всю ширину экрана: по нему водят пальцем и вводят длинные
   запросы, узкое нативное поле для этого не годится. */
.search-field { margin-bottom: 14px; }

.search-field input[type="search"] {
  width: 100%;
  max-height: none;
  height: 48px;
  padding: 12px 14px;
  font-size: 15px;
  -webkit-appearance: none;
  appearance: none;
}

.search-field input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* «Мои сообщения» — крупная кнопка сразу под шапкой профиля. */
.profile-messages-btn { margin: 4px 0 16px; }

/* Шапка экрана: «Назад» слева, действие справа. */
.screen-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}

.screen-topbar .back-btn { margin: 0; }

.topbar-action {
  padding: 9px 14px;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}

.topbar-action:active { transform: scale(.97); }

/* Компактная шапка профиля: кнопка сообщений стоит справа от аватарки, над именем —
   не наезжает на фото и не съедает отдельную полосу экрана. */
.profile-head-fields {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  flex: 1;
}

.profile-messages-btn {
  width: 100%;
  padding: 10px 12px;
  border: none;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  font-size: 14.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.profile-messages-btn:active { transform: scale(.98); }

.profile-delete-btn {
  width: 100%;
  padding: 12px;
  margin: 4px 0;
  border: none;
  border-radius: 10px;
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
  font-size: 14.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.profile-delete-btn:disabled { opacity: .5; }

/* Карточки профиля: только заголовок и число, по центру. Пояснение и «Открыть →» убраны —
   вся карточка и так кликабельна, а три строки текста на каждой съедали целый экран. */
/* Плитки профиля - те же, что на главной: по две в ряд. Карточки во всю ширину
   занимали два экрана ради пяти строк текста, и до нижней приходилось листать. */
.profile-grid { margin-bottom: 16px; }
.profile-tile { min-height: 104px; justify-content: flex-start; gap: 6px; }
.profile-tile .home-tile-icon { margin-bottom: 2px; }
.profile-tile .home-tile-sub { margin-top: auto; font-size: 13px; }

/* Значок статуса поставщика в профиле. */
.supplier-badge {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  padding: 5px 11px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
}

.supplier-badge-ok {
  background: var(--accent);
  color: #fff;
}

.supplier-badge-pending {
  background: #eceef1;
  color: var(--fg-dim);
}

/* Значок верификации покупателя. Красный — приглашение подтвердить номер одним тапом,
   зелёный — подтверждено. Синий остаётся за статусом поставщика. */
.supplier-badge {
  border: none;
  font-family: inherit;
  cursor: default;
}

.supplier-badge-verified {
  background: color-mix(in srgb, var(--ok) 16%, transparent);
  color: var(--ok);
}

.supplier-badge-unverified {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  color: var(--danger);
  cursor: pointer;
}

.supplier-badge-unverified:active { transform: scale(.97); }
.supplier-badge:disabled { opacity: .6; }

/* Имя и значок верификации в одной строке: значок справа, имя занимает остальное. */
.profile-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.profile-name-row .name-field-wrap { flex: 1; min-width: 0; }
.profile-name-row .supplier-badge { flex: 0 0 auto; align-self: center; }

/* Подтверждённый статус — сплошная зелёная плашка, а не бледная. */
.supplier-badge-verified {
  background: var(--ok);
  color: #fff;
}

/* Значок рядом с длинным именем: сам не сжимается и не переносится, а поле имени уступает
   ему место. Иначе строка разъезжается на две и съедает высоту экрана. */
.profile-name-row .supplier-badge {
  white-space: nowrap;
  padding: 5px 9px;
  font-size: 12px;
}

.profile-name-row .name-field-wrap { min-width: 80px; }
.profile-name-row .profile-name-input { min-width: 0; }

/* Затвор — красный круг, как у любой камеры: понятен без подписи. */
.respond-shutter {
  border-color: rgba(255, 255, 255, .9);
  background: #e5484d;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
}

.respond-shutter:active { background: #c93b3f; }

/* После съёмки на том же месте — тот же круг со стрелкой «переснять». Отдельная полоса
   во всю ширину под фото занимала целый экран и выглядела как чужеродная кнопка. */
.shutter-retake {
  background: rgba(20, 20, 20, .6);
  color: #fff;
  font-size: 26px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(6px);
}

.shutter-retake:active { background: rgba(20, 20, 20, .8); }

/* ── Съёмка: вспышка затвора и появление кадра ──────────────────────────────────────────────
   Между «нажал» и «вот фото» раньше не было ничего — кадр просто подменялся, и момент съёмки
   не читался вообще. Три коротких эффекта: затвор проседает под пальцем, экран моргает белым,
   снимок въезжает с лёгким увеличением. Всё до 400 мс, чтобы не мешать быстрой работе. */
.respond-shutter {
  transition: transform 0.12s ease, background 0.15s ease;
}

.respond-shutter:not(:disabled):active { transform: translateX(-50%) scale(0.88); }

.camera-flash::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 14px;
  background: #fff;
  animation: camera-flash 0.32s ease-out forwards;
  pointer-events: none;
}

@keyframes camera-flash {
  0% { opacity: 0.95; }
  100% { opacity: 0; }
}

.shot-appear { animation: shot-appear 0.34s cubic-bezier(0.2, 0.9, 0.3, 1); }

@keyframes shot-appear {
  0% { opacity: 0; transform: scale(1.04); }
  100% { opacity: 1; transform: scale(1); }
}

/* Кнопка «переснять» появляется с задержкой — сначала человек видит кадр, потом действие. */
.shutter-retake { animation: retake-in 0.3s ease-out 0.18s both; }

@keyframes retake-in {
  0% { opacity: 0; transform: translateX(-50%) scale(0.6) rotate(-90deg); }
  100% { opacity: 1; transform: translateX(-50%) scale(1) rotate(0); }
}

.shutter-retake:not(:disabled):active { transform: translateX(-50%) scale(0.88) rotate(-40deg); }

/* Над видоискателем системные жесты масштабирования и прокрутки не работают — щипок здесь
   принадлежит камере, а не странице. */
.respond-camera-wrap {
  /* pan-y, а НЕ none: одним пальцем страница должна листаться как обычно — иначе после съёмки
     человек упирается в снимок и не может прокрутить форму дальше. Щипок двумя пальцами
     перехватывается в коде и уходит в зум камеры. */
  touch-action: pan-y;
  overflow: hidden;
  border-radius: 14px;
}

/* Готовый снимок зумить нечего — на нём вообще никаких ограничений. */
#catalogPhotoPreviewWrap { touch-action: auto; }

.respond-camera-wrap .respond-video {
  transform-origin: center center;
  transition: transform 0.08s linear;
}

/* Потягивание за край страницы больше не «резинит» и не отдаёт жест окну Telegram —
   иначе на верхней границе списка свайп вниз закрывал приложение. */
html, body {
  overscroll-behavior: none;
  overscroll-behavior-y: none;
}

/* Вопросы под товаром: ответ визуально вложен, чтобы ветка читалась с одного взгляда. */
.comment-reply { margin-left: 20px; border-left: 3px solid var(--accent); }

.comment-tag {
  font-size: 10.5px;
  padding: 2px 7px;
  vertical-align: middle;
}

.comment-reply-btn {
  margin-top: 8px;
  padding: 6px 14px;
  font-size: 13px;
}

#productComments .earn-card { margin-bottom: 8px; }
#productBody .respond-video { max-height: 320px; object-fit: cover; border-radius: 14px; }

/* «Мои товары» — плитки с фото. Кнопки внутри плитки компактные, иначе одна карточка
   занимает пол-экрана и десять товаров невозможно окинуть взглядом. */
.my-tile { display: flex; flex-direction: column; }
.my-tile .tile-body { flex: 1; }

.tile-actions {
  padding: 0 10px 10px;
  gap: 6px;
  margin-top: 0;
}

.tile-actions button {
  padding: 7px 6px;
  font-size: 12px;
  border-radius: 8px;
}

.tile-hidden { color: var(--danger); }

/* Состояние товара в «Моих товарах»: ожидание, отказ и публикация различаются цветом —
   поставщик понимает ситуацию, не читая текст целиком. */
/* Было золото #b8860b - тот же жёлтый, только в мелком тексте. Терракота читается как
   «внимание» не хуже, но не спорит с зелёным и не выглядит выцветшей. */
.tile-pending { color: #9A6248; font-weight: 600; }
.tile-live { color: var(--ok); font-weight: 600; }

/* Кнопка «Профиль»: аватарка вдвое крупнее и прижата к левому краю кнопки, а подпись
   занимает остальное. Мелкий значок посреди текста читался как иконка, а не как фото
   человека — теперь сразу видно, чей это профиль. */
.quick-btn[data-quick="profile"] {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-left: 8px;
  padding-right: 10px;
  text-align: left;
}

/* Профилю столбец шире остальных двух. В равных третях его содержимое -
   аватар 36px, отступ и слово «Профиль» - занимало почти всю ширину, и слово
   упиралось в край кнопки. У «Ленты» и «Новинок» вместо аватара значок 16px,
   и запас ширины у них лишний: отдаём его туда, где не хватает.

   1.3fr, а не auto по содержимому: столбцы должны стоять на месте при любой
   длине подписи, иначе смена аватара или слова двигала бы соседние кнопки. */
/* Две кнопки вместо трёх - «Новинки» убраны. Столбцы теперь равные: у профиля
   аватар 36px, у витрины значок 16px, но на половине экрана места хватает
   обоим с запасом, и перекос долями стал бы заметен как кривизна. */
.home-quickbar { grid-template-columns: 1fr 1fr; }

.quick-btn[data-quick="profile"] .quick-avatar {
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  vertical-align: middle;
  margin-right: 0;
}

.quick-btn[data-quick="profile"] .quick-avatar-img,
.quick-btn[data-quick="profile"] .quick-avatar-icon {
  width: 36px;
  height: 36px;
  border-radius: 9px;
}

/* Цены услуг: название и пояснение сверху, поле с кнопкой — снизу. Кнопка «Сохранить»
   неактивна, пока число не изменилось: случайный тап не должен менять деньги. */
.price-row { display: flex; flex-direction: column; gap: 10px; }
.price-info .earn-card-desc { margin-top: 2px; }
.price-info .tile-line { font-size: 11.5px; opacity: .7; }

.price-edit { display: flex; gap: 8px; align-items: center; }

.price-input {
  flex: 1;
  max-height: 42px;
  padding: 10px 12px;
  font-size: 15px;
  font-weight: 600;
}

.price-edit .admin-approve-btn { flex: 0 0 auto; padding: 10px 16px; }
.price-edit .admin-approve-btn:disabled { opacity: .4; }

.blocked-mark { color: var(--danger); }

/* Счётчик в карточках админки — по левому краю, в одну линию с заголовком и описанием:
   по центру он выбивался из общей колонки текста. */
#adminView .earn-card-amount,
.admin-menu .earn-card-amount { text-align: left; }

/* Имя в профиле: пока не трогали — обычный серый текст. Открытое поле ввода на видном месте
   провоцирует «а что сюда вписать?», хотя имя меняют раз в жизни. */
.profile-name-input {
  color: var(--fg-dim);
  background: transparent;
  border-color: transparent;
  transition: color .15s, background .15s, border-color .15s;
}

.profile-name-input.editing,
.profile-name-input:focus {
  color: var(--fg);
  background: var(--card);
  border-color: var(--border, #e3e6ea);
}

/* Свой лист действий — не зависит от версии Telegram и не ограничен тремя кнопками. */
.sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .4);
  display: flex;
  align-items: flex-end;
  z-index: 60;
}

.sheet {
  width: 100%;
  background: #fff;
  border-radius: 18px 18px 0 0;
  padding: 14px 14px calc(14px + env(safe-area-inset-bottom));
  animation: sheet-up .2s ease-out;
}

@keyframes sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }

.sheet-title { font-size: 13px; color: var(--fg-dim); text-align: center; margin-bottom: 10px; }

.sheet-btn {
  width: 100%;
  padding: 14px;
  margin-bottom: 8px;
  border: none;
  border-radius: 12px;
  background: var(--card);
  color: var(--fg);
  font-size: 15.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.sheet-danger { color: var(--danger); }
.sheet-cancel { margin-bottom: 0; color: var(--fg-dim); }

/* Фото на модерации во весь экран: по превью не видно ни бирки, ни качества. */
.photo-zoom {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .92);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 70;
  cursor: zoom-out;
}

.photo-zoom img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* Раскрывающаяся строка в списках админки. */
.person-wrap { margin-bottom: 8px; }
.person-wrap .admin-row { margin-bottom: 0; width: 100%; }

.person-chevron { flex: 0 0 auto; color: var(--fg-dim); font-size: 13px; }

.person-details {
  padding: 12px 14px 14px;
  background: var(--card);
  border-radius: 0 0 14px 14px;
  margin-top: -6px;
}

.person-supplier { margin-top: 12px; padding-top: 12px; border-top: 1px solid #e9ecef; }

/* Заголовок и действие в одной строке: отдельная полоса во всю ширину под кнопку
   «Заблокированные» съедала экран ради того, что нужно раз в неделю. */
.heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.heading-row .projects-heading { margin: 0; }

/* Два действия справа от заголовка. На узком экране они переносятся под него целой
   группой, а не разваливаются по одной кнопке в строку. */
.heading-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.heading-row { flex-wrap: wrap; }

.heading-action {
  flex: 0 0 auto;
  padding: 7px 12px;
  border: none;
  border-radius: 999px;
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  white-space: nowrap;
  cursor: pointer;
}

.heading-action:active { transform: scale(.97); }

/* «Онлайн» — зелёная, в тон точке на аватарках: один и тот же смысл должен иметь один цвет. */
.heading-action-ok { background: color-mix(in srgb, #22c55e 15%, transparent); color: #15803d; }

/* Пульс только пока кнопка действительно зовёт в список онлайна. Когда она переключилась в
   «Все · N», зелёное мигание врало бы: живого счётчика за ней в этот момент нет. */
@keyframes heading-ok-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
  50%      { box-shadow: 0 0 0 5px rgba(34, 197, 94, .20); }
}

.heading-action-ok.is-live { animation: heading-ok-pulse 2.2s ease-in-out infinite; }

/* Уважаем системную настройку «уменьшить движение»: постоянное мигание на экране — первое,
   что мешает людям, которые её включили. Цвет при этом остаётся, смысл не теряется. */
@media (prefers-reduced-motion: reduce) {
  .ava-dot-on,
  .heading-action-ok.is-live { animation: none; }
  .ava-dot-on { box-shadow: 0 0 0 1px rgba(34, 197, 94, .35); }
}

/* Просмотр аватарки: фото по центру, действия поверх него снизу. */
.avatar-viewer-img {
  max-width: 86%;
  max-height: 62%;
  object-fit: contain;
  border-radius: 16px;
}

.avatar-viewer-empty {
  color: rgba(255, 255, 255, .7);
  font-size: 15px;
}

.avatar-viewer-actions {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: calc(20px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 8px;
  cursor: default;
}

.viewer-btn {
  width: 100%;
  padding: 14px;
  border: none;
  border-radius: 12px;
  background: rgba(255, 255, 255, .95);
  color: #17181a;
  font-size: 15.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  backdrop-filter: blur(8px);
}

.viewer-btn:active { transform: scale(.98); }
.viewer-danger { color: var(--danger); }

/* Отступ под строкой заголовка: кнопка «Заблокированные» упиралась в поле поиска. */
.heading-row { margin-bottom: 14px; }

/* Карточка товара: название, цена и категория одной строкой. */
.product-head {
  font-size: 17px;
  line-height: 1.45;
  margin-top: 12px;
}

.product-name { font-weight: 700; }
.product-price { font-weight: 600; }
.product-sep { color: var(--fg-dim); margin: 0 2px; }
.product-cat { font-size: 15px; color: var(--fg-dim); }

.product-desc { margin-top: 8px; font-size: 14.5px; color: var(--fg-dim); line-height: 1.5; }

/* Аватарка с галочкой подтверждения — как в мессенджерах: значок в правом нижнем углу. */
.ava-box { position: relative; flex: 0 0 auto; }

.ava-verified {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--ok);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid #fff;
}

.ava-verified-big { width: 22px; height: 22px; font-size: 13px; }

/* Статусы человека — плашками сверху карточки, чтобы считывались без чтения полей. */
.person-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }

.tag {
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}

.tag-ok { background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok); }
.tag-warn { background: #F3ECE8; color: #9A6248; }
.tag-blue { background: var(--accent); color: #fff; }
.tag-grey { background: #eceef1; color: var(--fg-dim); }
.tag-danger { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); }

.person-handle { font-size: 13.5px; font-weight: 500; color: var(--fg-dim); margin-top: 2px; }

/* Значок состояния — в правом верхнем углу аватарки, как в мессенджерах. Он вынесен ЗА
   границу фото, поэтому не накладывается на лицо, что бы человек ни загрузил. */
.ava-mark {
  position: absolute;
  right: -3px;
  top: -3px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 999px;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  border: 2px solid #fff;
  box-sizing: border-box;
}

.ava-mark-big { min-width: 22px; height: 22px; font-size: 13px; line-height: 18px; top: -4px; right: -4px; }

/* Коробка аватарки по размеру самой аватарки, без внутренних отступов: любой отступ здесь
   сдвигал фото вниз и вправо относительно текста, и строка переставала быть ровной. Значки
   выступают за край на пару пикселей — запаса внутри карточки (16-18px) для этого хватает. */
.ava-box { line-height: 0; }

/* Точка «онлайн/офлайн» — ВВЕРХУ СЛЕВА, напротив метки верификации, чтобы два значка
   никогда не наезжали друг на друга. Ярко-зелёная = заходил в последние 15 минут. */
.ava-dot {
  position: absolute;
  left: -2px;
  top: -2px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-sizing: border-box;
  z-index: 1;
}

.ava-dot-big { width: 17px; height: 17px; left: -3px; top: -3px; }

/* Зелёный берём ярче системного --ok: точка маленькая, приглушённый цвет на ней не читается.
   Пульсирующее кольцо расходится и гаснет — так точка читается как «живой прямо сейчас», а не
   как ещё одна нарисованная метка. Мигание включено ТОЛЬКО у зелёной: серая офлайн-точка
   должна оставаться неподвижной, иначе в списке рябит от движения на каждой строке. */
@keyframes ava-dot-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(34, 197, 94, .55); }
  70%  { box-shadow: 0 0 0 8px rgba(34, 197, 94, 0); }
  100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
}

.ava-dot-on {
  background: #22c55e;
  animation: ava-dot-pulse 2.2s cubic-bezier(.4, 0, .2, 1) infinite;
}

.ava-dot-off { background: #b6bcc4; }

/* На своей аватарке в профиле — квадрат со скруглением, а не круг, поэтому точку смещаем
   внутрь угла, иначе она висит в воздухе. */
.profile-avatar-wrap { position: relative; }
.ava-dot-square { left: 4px; top: 4px; width: 16px; height: 16px; }

.ava-mark-ok { background: var(--ok); }
.ava-mark-supplier { background: var(--accent); }
.ava-mark-warn { background: var(--danger); }

/* Просмотр аватарки: кнопки лежат НА фото, тремя квадратами внизу снимка. */
.avatar-viewer-frame {
  position: relative;
  max-width: 90%;
  max-height: 78%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.avatar-viewer-frame .avatar-viewer-img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 16px;
  display: block;
}

.avatar-viewer-actions {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 12px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: 8px;
  cursor: default;
}

.viewer-tile {
  padding: 12px 6px;
  border: none;
  border-radius: 12px;
  background: rgba(255, 255, 255, .92);
  color: #17181a;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  line-height: 1.25;
  cursor: pointer;
  backdrop-filter: blur(8px);
}

.viewer-tile:active { transform: scale(.96); }
.viewer-tile.viewer-danger { color: var(--danger); }

/* Обсуждение под товаром: аватарка автора со значком статуса рядом с именем. */
.comment-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.comment-who { min-width: 0; font-size: 14.5px; }
.comment-tag { font-size: 10.5px; padding: 2px 7px; vertical-align: middle; }
.comment-text { font-size: 14.5px; line-height: 1.5; overflow-wrap: anywhere; }

/* Технические данные: обведённый блок, значения синим и копируются тапом. */
.admin-tech[open] {
  border: 1px solid #dfe3e8;
  border-radius: 12px;
  padding: 4px 12px 10px;
  background: #fbfcfd;
}

.admin-tech[open] summary { color: var(--fg); }

.tech-body { margin-top: 4px; }

.tech-copy {
  color: var(--accent);
  font-weight: 600;
  cursor: pointer;
  border-bottom: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
}

.tech-copy:active { opacity: .6; }

/* Рекламный баннер вверху главной. Пустое место продаёт себя само: показывает вчерашнюю
   посещаемость и цену, посчитанную от неё. */
.ad-banner {
  display: block;
  width: 100%;
  padding: 14px 16px;
  margin-bottom: 14px;
  border: none;
  border-radius: 14px;
  background: linear-gradient(135deg, #1f6feb, #3b8bff);
  color: #fff;
  text-align: left;
  font-family: inherit;
  cursor: pointer;
}

.ad-banner:active { transform: scale(.99); }

.ad-banner-empty {
  background: repeating-linear-gradient(135deg, #f3f5f8, #f3f5f8 10px, #eef1f5 10px, #eef1f5 20px);
  color: var(--fg);
  border: 1px dashed #c9d1db;
}

.ad-banner-title { font-size: 15.5px; font-weight: 700; letter-spacing: .2px; }
.ad-banner-sub { font-size: 12.5px; margin-top: 4px; opacity: .9; line-height: 1.4; }

/* Баннер первым экраном: его видит каждый вошедший, поэтому он должен цеплять взгляд,
   но не мешать — движение медленное и не дёргает интерфейс. */
.ad-banner {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  margin-bottom: 10px;
  border: none;
  border-radius: 14px;
  overflow: hidden;
  font-family: inherit;
  cursor: pointer;
  background: linear-gradient(120deg, #1f6feb, #7b5cff, #1f6feb);
  background-size: 220% 100%;
  animation: ad-flow 7s ease-in-out infinite;
  box-shadow: 0 4px 16px rgba(31, 111, 235, .28);
}

@keyframes ad-flow {
  0% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

/* Блик, проезжающий по баннеру раз в несколько секунд. */
.ad-banner-glow {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 40%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .28), transparent);
  animation: ad-shine 4.5s ease-in-out infinite;
  pointer-events: none;
}

@keyframes ad-shine {
  0% { left: -45%; }
  55% { left: 105%; }
  100% { left: 105%; }
}

.ad-banner-body { position: relative; display: block; padding: 13px 16px; text-align: center; }
.ad-banner-title { display: block; font-size: 15px; font-weight: 800; letter-spacing: .6px; color: #fff; }
.ad-banner-sub { display: block; font-size: 12px; margin-top: 3px; color: rgba(255, 255, 255, .92); line-height: 1.35; }

.ad-banner:active { transform: scale(.99); }

/* Купленная реклама — спокойнее: она уже продана, привлекать к месту внимание незачем. */
.ad-banner.ad-banner-sold { animation: none; background: linear-gradient(120deg, #16324f, #1f4d72); }
.ad-banner.ad-banner-sold .ad-banner-glow { display: none; }

/* Баннер — крупный первый экран: он продаёт место, и мелким его делать бессмысленно. */
.ad-banner { margin-bottom: 0; border-radius: 16px; }
.ad-banner-body { padding: 22px 18px; }
.ad-banner-title { font-size: 19px; letter-spacing: 1px; }
.ad-banner-sub { font-size: 13px; margin-top: 6px; }

/* Разделитель между рекламой и содержимым шапки: рекламу нельзя путать с интерфейсом. */
.topbar-divider {
  height: 1px;
  background: #e6e9ee;
  margin: 12px 0 10px;
}

/* «Зарабатывай с нами» переехала вниз главной, над «Предложите идею». */
.home-earn-bottom { margin: 4px 0 10px; }

/* ── Баннер: деловой прямоугольник ─────────────────────────────────────────────────────────
   Скруглений почти нет, цвет строгий, движение сдержанное: это рекламное место для оптовиков,
   а не игровой промо-блок. Кричащий градиент здесь работает против доверия. */
.ad-banner {
  border-radius: 6px;
  margin: 0;
  background: linear-gradient(100deg, #16202e 0%, #1d2c40 45%, #16202e 100%);
  background-size: 200% 100%;
  animation: ad-flow 12s linear infinite;
  box-shadow: none;
  border: 1px solid #24334a;
}

.ad-banner-body { padding: 18px 18px; text-align: left; }

.ad-banner-title {
  display: block;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: #eaf1ff;
}

.ad-banner-sub {
  display: block;
  font-size: 12.5px;
  margin-top: 6px;
  color: #9fb3d0;
  line-height: 1.45;
}

/* Блик медленный и слабый — обозначает, что место живое, но не мигает в лицо. */
.ad-banner-glow {
  width: 30%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .10), transparent);
  animation: ad-shine 9s ease-in-out infinite;
}

.topbar-divider { margin: 14px 0 12px; }

/* Экран предложения рекламы. */
.pitch-hero {
  text-align: center;
  padding: 18px 12px 14px;
  background: #16202e;
  border-radius: 6px;
  margin-bottom: 14px;
}

.pitch-price { font-size: 34px; font-weight: 800; color: #fff; line-height: 1; }
.pitch-price-note { font-size: 12.5px; color: #9fb3d0; margin-top: 6px; }

/* Расписание рекламы: сетка суток, свободные кликабельны. */
.schedule-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }

.schedule-cell {
  padding: 10px 4px;
  border: 1px solid #dfe3e8;
  border-radius: 8px;
  background: #fff;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.schedule-cell.schedule-busy { background: #f3f5f8; color: var(--fg-dim); cursor: default; }
.schedule-date { font-size: 14px; font-weight: 700; }
.schedule-state { font-size: 11px; color: var(--fg-dim); }
.schedule-cell:not(.schedule-busy) .schedule-state { color: var(--accent); font-weight: 600; }

.pitch-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }

/* Предпросмотр поста — тот же вид, что будет на главной. */
.ad-preview {
  margin: 6px 0 14px;
  padding: 16px;
  border-radius: 6px;
  background: linear-gradient(100deg, #16202e, #1d2c40);
  border: 1px solid #24334a;
}

.ad-preview-title { font-size: 14px; font-weight: 700; letter-spacing: 1.4px; color: #eaf1ff; }
.ad-preview-sub { font-size: 12.5px; margin-top: 6px; color: #9fb3d0; }

/* Анимированный фон убран: он осветлил подложку, и карточки на ней перестали читаться —
   белые плитки слились с фоном. Контраст между содержимым и фоном важнее эффекта. */

/* Модальные окна по центру экрана: раздел не должен уезжать вниз, теряя место прокрутки. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 80;
}

.modal-card {
  width: 100%;
  max-width: 460px;
  max-height: 82vh;
  overflow-y: auto;
  background: #fff;
  border-radius: 16px;
  padding: 16px;
  animation: modal-in .18s ease-out;
}

@keyframes modal-in { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: scale(1); } }

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 12px;
}

.modal-close {
  border: none;
  background: none;
  font-size: 18px;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 4px 8px;
}

/* Картинка в баннере — фоном под затемнением, текст читается поверх. */
/* Баннер с фото получает ЖЁСТКУЮ форму. Без неё высота тянулась за длиной текста: два
   слова - один баннер, четыре - другой, и кадр, выбранный в обрезке, никогда не совпадал
   с тем, что показано. Теперь форма постоянна и равна той, под которую кадрируют. */
.ad-banner-photo {
  background-size: cover;
  background-position: center;
  aspect-ratio: 3.2 / 1;
  display: flex;
  align-items: flex-end;
}

.ad-banner-photo .ad-banner-body { width: 100%; }
.ad-banner-photo .ad-banner-body {
  background: linear-gradient(180deg, rgba(10, 16, 26, .10), rgba(10, 16, 26, .78));
  width: 100%;
}

/* Тень под текстом. Затемнения снизу мало: если человек загрузит светлый снимок, белые
   буквы на нём растворятся именно там, где градиент ещё прозрачный. Тень работает на
   любом фоне и не зависит от того, что именно на фотографии. */
/* ОБВОДКА по буквам, а не только тень. Тень размывается и на пёстрой картинке всё равно
   даёт слияние; тонкий тёмный контур отделяет букву от фона при любом снимке - именно это
   и не давало читать заголовок поверх логотипа.

   paint-order кладёт обводку ПОД заливку буквы: без него контур наползает на саму букву
   и съедает её тонкие части. */
.ad-banner-photo .ad-banner-title,
.ad-banner-photo .ad-banner-sub {
  -webkit-text-stroke: var(--ad-stroke, 0.6px) rgba(0, 0, 0, .55);
  paint-order: stroke fill;
}
/* Саму тень собирает скрипт и кладёт готовой строкой: у объёмных букв это не одна тень, а
   стопка слоёв со сдвигом на пиксель, и посчитать её в стилях нечем. */
.ad-banner-photo .ad-banner-title { text-shadow: var(--ad-shadow, 0 1px 2px rgba(0, 0, 0, .6), 0 2px 12px rgba(0, 0, 0, .45)); }
.ad-banner-photo .ad-banner-sub   { text-shadow: var(--ad-shadow-sub, 0 1px 2px rgba(0, 0, 0, .6)); }

.ad-banner-photo .ad-banner-sub { -webkit-text-stroke-width: calc(var(--ad-stroke, 0.6px) * .66); }

.ad-preview { overflow: hidden; padding: 0; }
.ad-preview-img { width: 100%; max-height: 140px; object-fit: cover; display: block; }
.ad-preview-text { padding: 14px 16px; }

/* Живой график за сутки: три линии, без внешних библиотек. */
/* Боковые поля срезаны: график тянется во всю ширину карточки, а заголовок и легенда
   получают свой отступ отдельно - иначе ради двух текстовых строк терялась ширина. */
.chart-card {
  background: #fff;
  border: 1px solid #e6e9ee;
  border-radius: 12px;
  padding: 12px 4px 10px;
  margin-bottom: 12px;
}

.chart-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fg-dim);
  margin-bottom: 6px;
}

.chart-live { display: inline-flex; align-items: center; gap: 5px; color: var(--ok); }

.chart-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ok);
  animation: chart-pulse 1.8s ease-in-out infinite;
}

@keyframes chart-pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

.chart-box { width: 100%; }
.chart-max { font-size: 11px; color: var(--fg-dim); text-align: right; margin-top: 2px; }

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  font-size: 11.5px;
  color: var(--fg-dim);
  margin-top: 6px;
}

.chart-legend .dot { display: inline-block; width: 9px; height: 3px; border-radius: 2px; margin-right: 4px; vertical-align: middle; }
/* Просмотры красным пунктиром, переходы синей сплошной - как на графике. */
.dot-visits { background: repeating-linear-gradient(90deg, #e5484d 0 2px, transparent 2px 5px); }
/* .dot-views удалён. Класса с таким именем в разметке нет - легенда рисуется через
   .dot-visits, - а правила остались с прежней версии графика и держали единственный
   оставшийся жёлтый в приложении, причём цвет уже не совпадал с линией: просмотры на
   графике красные. Мёртвые правила не «на всякий случай», они сбивают при следующей правке.
   Переходы - СПЛОШНОЙ синей, как линия на графике. */
.dot-clicks { background: #2563eb; }

/* Подсказка под графиком: значения того часа, которого коснулись. */
/* Подсказка лежит В ЛЕВОМ ВЕРХНЕМ УГЛУ САМОГО графика, а не строкой над ним.
   Держа палец на линии, человек смотрит на график, а не поверх него - и число
   должно быть там же, куда направлен взгляд. Угол выбран левый верхний:
   правый занят отметкой «онлайн», а низ - подписями дат.

   Поверх линий, но сквозная для касаний: иначе она перехватывала бы палец
   у самого края, где как раз лежат первые точки. */
.chart-tip {
  position: absolute; left: 6px; top: 4px; z-index: 2;
  max-width: calc(100% - 12px);
  font-size: 12.5px; line-height: 1.35;
  color: var(--fg);
  text-align: left;
  opacity: 0;
  transition: opacity .15s;
  padding: 4px 9px;
  border-radius: 10px;
  background: rgba(255, 255, 255, .88);
  backdrop-filter: blur(4px);
  pointer-events: none;
}

.chart-tip.visible { opacity: 1; }
/* Рамка для угловой подсказки: без относительного положения она уехала бы
   в угол всей страницы. */
#adChart { position: relative; }
.chart-card { padding: 12px 4px 10px; }

/* Выбор даты внутри формы поста. */
.schedule-compact { grid-template-columns: repeat(4, 1fr); gap: 6px; margin-bottom: 8px; }
.schedule-compact .schedule-cell { padding: 8px 2px; }
.schedule-picked { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, #fff); }

/* Пояснения к графику и цене — раскрываются, а не кричат сразу. */
.chart-help { margin-top: 8px; }
.chart-help summary, .price-help summary { font-size: 12.5px; }
.price-help { margin-bottom: 14px; }
.price-help[open] { background: #fbfcfd; }

/* Сводка над графиком: при перетаскивании пальцем низ карточки закрыт рукой. */
/* Подсказка на графике рекламы - в том же ключе, что и сама карточка:
   светлая, со скруглением карточки и её же тенью. Она часть графика,
   а не всплывающее окно поверх него. */
#adChart .chart-tip {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 2px 10px rgba(20, 24, 28, .07);
  padding: 6px 10px;
  font-size: 12px;
  white-space: nowrap;
}
/* .dot-peak убран вместе с лентой активных часов - см. drawChart. */

/* Крестик закрытия — в рамке: без неё он читается как часть заголовка, а не как кнопка. */
.modal-close {
  width: 30px;
  height: 30px;
  border: 1px solid #dfe3e8;
  border-radius: 8px;
  background: #fff;
  line-height: 1;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-close:active { background: #f1f3f6; }

/* Услуга без заданной цены — приглушённое поле с подсказкой. */
.price-input::placeholder { color: #b6bcc5; font-weight: 500; }

/* Крестик закрытия — в рамке: без неё он читается как значок, а не как кнопка. */
.modal-close {
  width: 32px;
  height: 32px;
  border: 1px solid #dfe3e8;
  border-radius: 8px;
  background: #fff;
  font-size: 15px;
  line-height: 1;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-close:active { background: #f3f5f8; }

/* ── Экран сбоя запуска ────────────────────────────────────────────────────────────────────
   Показывается поверх всего, когда приложение не смогло запуститься. Своя вёрстка, не
   зависящая ни от одного класса приложения: если сломался app.js, полагаться на его разметку
   нельзя, а человек не должен смотреть в пустое окно. */
.boot-failure {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #fff;
  padding: 28px 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 14px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: #101828;
}

.boot-failure-title { font-size: 19px; font-weight: 700; }
.boot-failure-text { font-size: 14.5px; color: #667085; line-height: 1.5; }

.boot-failure-tech {
  margin: 0;
  padding: 12px 14px;
  border-radius: 12px;
  background: #f5f6f8;
  color: #475467;
  font-size: 12px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
}

.boot-failure-btn {
  padding: 14px 18px;
  border: none;
  border-radius: 14px;
  background: #2f80ed;
  color: #fff;
  font-size: 16px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

/* Журнал сбоев запуска в админке — моноширинный, читается как лог, а не как карточка. */
.boot-log-item { margin-bottom: 8px; }
.boot-log-head { font-size: 14px; font-weight: 600; }
.boot-log-meta { margin-top: 4px; font-size: 12.5px; color: var(--fg-dim); line-height: 1.6; word-break: break-word; }

/* ── Мои размещения (реклама) ──────────────────────────────────────────────────────────────
   Карточка отвечает на три вопроса подряд: что вышло, сколько это дало, в какие часы. */
.ad-mine-card { margin-bottom: 12px; }

.ad-mine-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 10px;
}

/* Превью — тот же тёмный баннер, что стоит на первом экране: человек должен увидеть ровно
   то, что видели люди, а не пересказ своими словами. */
.ad-mine-preview {
  position: relative;
  border-radius: 14px;
  padding: 16px 18px;
  background: #121a26;
  background-size: cover;
  background-position: center;
  min-height: 76px;
  display: flex;
  align-items: center;
  overflow: hidden;
}

.ad-mine-preview.has-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(10, 14, 20, .88), rgba(10, 14, 20, .55));
}

.ad-mine-preview-body { position: relative; z-index: 1; }
.ad-mine-preview-title { color: #fff; font-size: 15px; font-weight: 700; letter-spacing: .3px; }
.ad-mine-preview-sub { margin-top: 4px; color: rgba(255, 255, 255, .72); font-size: 13px; line-height: 1.45; }

.ad-mine-stats { display: flex; gap: 8px; margin-top: 12px; }

.ad-mine-stat {
  flex: 1;
  padding: 10px 6px;
  border-radius: 12px;
  background: #f5f6f8;
  text-align: center;
}

.ad-mine-stat b { display: block; font-size: 18px; }
.ad-mine-stat span { font-size: 11.5px; color: var(--fg-dim); }

/* Часовая полоса: высота столбика — просмотры, тёмная часть снизу — переходы. */
.ad-hours { margin-top: 14px; }

.ad-hours-bars {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 54px;
}

.ad-hours-col { flex: 1; height: 100%; display: flex; align-items: flex-end; }

.ad-hours-bar {
  position: relative;
  width: 100%;
  border-radius: 3px 3px 0 0;
  background: color-mix(in srgb, var(--accent) 32%, transparent);
  min-height: 2px;
}

.ad-hours-click {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  border-radius: 3px 3px 0 0;
  background: var(--accent);
}

.ad-hours-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 4px;
  font-size: 10.5px;
  color: var(--fg-dim);
}

.ad-hours-note { margin-top: 8px; font-size: 12.5px; color: var(--fg-dim); }

.ad-mine-link {
  margin-top: 8px;
  font-size: 12px;
  color: var(--fg-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Плитка «Сейчас» — первой и во всю ширину: это главный ответ на «а можно прямо сегодня?».
   Раньше сегодняшняя дата стояла серой заглушкой «сегодня», и человек уходил ждать полуночи. */
/* «Сейчас» занимает всю ширину - этого достаточно, чтобы выделить её среди дат.
   Раньше здесь стояли рамка и подсветка цветом выделения, причём с !important:
   плитка выглядела ВЫБРАННОЙ всегда, даже когда выбраны были другие сутки.
   Человек читал это как «реклама намертво стоит на сегодня». Выделение теперь
   рисует только .schedule-picked - то же, что у остальных плиток. */
.schedule-now { grid-column: 1 / -1; }
.schedule-now .schedule-date { color: var(--fg); }
.schedule-now.schedule-busy .schedule-date { color: var(--fg-dim); }

/* Экран проверки опубликованной рекламы. */
.ad-review-meta { margin-top: 12px; line-height: 1.7; }
.ad-review-remove { margin-top: 18px; }

.ad-review-remove textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  font-family: inherit;
  font-size: 15px;
  resize: none;
}

/* Кнопка снятия — красная, но не первой: основное действие «Оставить», снятие всегда реже. */
.ad-review-remove-btn {
  margin-top: 10px;
  width: 100%;
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
}

.ad-mine-reason { margin-top: 12px; }
.ad-mine-fix { margin-top: 10px; }

/* Свой товар: срок жизни и удаление прямо на экране товара, а не в отдельном разделе. */
.product-own {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.product-own-note {
  margin-bottom: 10px;
  font-size: 13px;
  color: var(--fg-dim);
  line-height: 1.5;
}

.product-own .admin-reject-btn { width: 100%; }

/* Кнопка заказа в пустом разделе «Мои размещения»: у .submit-btn нет отступа сверху,
   вплотную к тексту она читается как его продолжение, а не как действие. */
#adMineList .submit-btn { margin-top: 14px; }

/* Крестик удаления на своей плитке — быстрый путь, не заходя в карточку. Тёмная подложка
   с размытием: плитка это фото, и голый значок на светлом снимке просто теряется. */
.tile-del {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: rgba(16, 24, 40, .58);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  color: #fff;
  font-size: 14px;
  line-height: 28px;
  font-family: inherit;
  text-align: center;
  padding: 0;
  cursor: pointer;
  z-index: 2;
}

.tile-del:active { transform: scale(.92); background: rgba(16, 24, 40, .78); }
.tile-del:disabled { opacity: .5; }

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

   Подложка полупрозрачная и с размытием: плитка это фото, и голый значок на светлом
   снимке просто теряется. */
.tile-edit {
  position: absolute;
  top: 6px;
  right: 40px;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: rgba(16, 24, 40, .58);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  color: #fff;
  padding: 0;
  cursor: pointer;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Значок мельче, чем на баннере: кружок 28px против 34px. */
.tile-edit .pencil-ico { width: 15px; height: 16px; }

.tile-edit:active { transform: scale(.92); background: rgba(16, 24, 40, .78); }

@media (hover: hover) {
  .tile-edit:hover { background: rgba(16, 24, 40, .78); }
}

/* Вкладки списка пользователей: Все / Онлайн / Заблокированные. Равная ширина — ни одна не
   выглядит главной, и палец попадает по любой одинаково. */
.user-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
  padding: 4px;
  border-radius: 999px;
  background: #f0f2f5;
}

.user-tab {
  flex: 1 1 0;
  min-width: 0;
  padding: 9px 6px;
  border: none;
  border-radius: 999px;
  background: none;
  color: var(--fg-dim);
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  transition: background .15s, color .15s;
}

/* Активная — белой «таблеткой» с тенью: видно, где ты, не читая подписей. */
.user-tab.is-active {
  background: #fff;
  color: var(--fg);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 2px 6px rgba(16, 24, 40, .08);
}

.user-tab:active { opacity: .8; }

/* Пульс — только на НЕактивной «Онлайн»: она зовёт посмотреть, кто сейчас в приложении.
   На открытой вкладке мигание было бы просто рябью. */
.user-tab.is-live:not(.is-active) { animation: heading-ok-pulse 2.2s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .user-tab.is-live:not(.is-active) { animation: none; }
}

/* Строка сортировки — одна полоса, а не блок фильтров: порядок меняют редко, а место под
   вкладками нужно самому списку. */
.sort-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}

.sort-label { font-size: 12.5px; color: var(--fg-dim); flex: 0 0 auto; }

.sort-select {
  flex: 1;
  min-width: 0;
  height: 34px;
  padding: 0 10px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  font-size: 13px;
  font-family: inherit;
}

/* Журнал сбоев: действия над всем списком и удаление отдельной записи. */
.boot-log-actions { display: flex; gap: 8px; margin-bottom: 12px; }
.boot-log-actions button { flex: 1; margin: 0; }

/* Лог — моноширинным, с сохранением переносов: тайминги и этапы читаются только столбиком,
   в сплошном абзаце они превращаются в кашу. */
.boot-log-meta {
  margin: 6px 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--fg-dim);
  white-space: pre-wrap;
  word-break: break-word;
}

.boot-log-del { width: 100%; margin-top: 10px; }

/* Кнопка выбора фото в заявке. Свой выбор вместо системного поля: системное на iPhone и
   Android открывает разное, и половина вариантов всегда была недоступна. */
/* Фото - первое и единственное обязательное действие формы: без него нет ни
   категории, ни рассылки поставщикам. Пунктирная серая рамка читалась как
   подпись к полю, а не как кнопка, и человек искал, куда жать. Теперь это
   та же хвойная кнопка действия, что и «Отправить», с бликом (Кирилл 2026-09-17). */
.photo-pick-btn {
  width: 100%;
  margin-top: 6px;
  padding: 14px;
  border-radius: 16px 4px 16px 4px;
  border: none;
  background: var(--pine);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  box-shadow: 0 3px 8px rgba(14, 107, 91, .20), 0 10px 24px rgba(14, 107, 91, .14);
}

.photo-pick-btn:active { opacity: .8; }

/* Звёздочка обязательного поля. Красная и чуть крупнее подписи — она должна считываться
   боковым зрением при прокрутке, а не находиться при вчитывании. */
.req {
  color: var(--danger);
  margin-left: 3px;
  font-weight: 700;
}

.form-req-note {
  margin: -6px 0 16px;
  font-size: 12.5px;
  color: var(--fg-dim);
}

/* Окно приглашения — по центру экрана. Раньше ссылка раскрывалась под карточкой: её
   приходилось искать прокруткой, а на двух карточках она была одна и та же. */
.invite-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 0;
  padding: 12px;
  border-radius: 12px;
  background: var(--card);
  border: 1px solid var(--border);
}

/* Ссылка длинная — обрезаем многоточием, а не переносим на три строки: читать её целиком
   всё равно не будут, копирование и отправка рядом. */
.invite-link {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.invite-copy {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  color: var(--fg);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}

.invite-copy:active { opacity: .75; }
.invite-send { margin-top: 12px; }
.invite-note { margin-top: 12px; }

/* Пояснение под цифрами размещения: показы и просмотры считаются по-разному, и без строчки
   разница выглядит как ошибка в данных. */
.ad-mine-note {
  margin-top: 8px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--fg-dim);
}

/* Плашка с ценой — кнопка. Решение о покупке принимается в момент взгляда на цену, и
   отправлять человека искать другую кнопку ниже значит терять часть решившихся. */
.pitch-hero {
  display: block;
  width: 100%;
  border: none;
  font-family: inherit;
  text-align: center;
  cursor: pointer;
}

.pitch-hero:active { transform: scale(.99); }

/* Выбранные сутки — их может быть несколько сразу, поэтому отметка должна читаться на
   нескольких плитках одновременно, а не как «одна активная». */
.schedule-cell.schedule-picked::after {
  content: "✓";
  position: absolute;
  top: 4px;
  right: 6px;
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
}

.schedule-cell { position: relative; }

/* Разобранная заявка уходит с экрана. Раньше она бледнела и оставалась навсегда — через
   десяток решений экран превращался в список разобранного, среди которого приходилось
   выискивать то, что ещё ждёт ответа. */
.earn-card.card-done {
  opacity: .55;
  transition: opacity .25s ease;
}

/* Сворачиваем высоту и отступы разом, иначе карточка исчезает, а пустое место остаётся. */
.earn-card.card-collapse {
  opacity: 0;
  max-height: 0;
  margin-top: 0;
  margin-bottom: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0;
  overflow: hidden;
  transform: translateX(24px);
  transition: opacity .22s ease, max-height .26s ease, margin .26s ease,
              padding .26s ease, transform .26s ease;
}

/* Без явной высоты max-height анимировать нечего: у карточки она auto. */
.earn-card.card-done { max-height: 600px; }

@media (prefers-reduced-motion: reduce) {
  .earn-card.card-done,
  .earn-card.card-collapse { transition: none; }
}

/* Рынок на плитке товара: покупателю важно не только что за товар, но и куда за ним ехать. */
.tile-market { color: var(--fg-dim); font-size: 12px; }

/* Вторая строка карточки товара: категория и рынок через разделитель. Отдельно от названия —
   его читают первым, а это уточнения. */
.product-meta {
  margin-top: 6px;
  font-size: 14px;
  color: var(--fg-dim);
}

.product-market { white-space: nowrap; }

/* Контакт поставщика в карточке товара — над разделителем, сразу под самим товаром. */
.product-contact-btn { margin-top: 16px; }

.product-contact-out {
  margin-top: 12px;
  font-size: 15px;
  line-height: 1.7;
  word-break: break-word;
}

/* Окно «не хватает средств» — по центру, с двумя выходами вместо тупика. */
.funds-card { text-align: center; }
.funds-title { font-size: 18px; font-weight: 700; margin-bottom: 10px; }

.funds-text {
  font-size: 14px;
  line-height: 1.55;
  color: var(--fg-dim);
  margin-bottom: 18px;
}

.funds-buy { margin-top: 10px; }

/* Контакты под кнопкой профиля: номер нужен сразу, не заставляя открывать профиль. */
.product-contact-rows { margin-top: 12px; line-height: 1.8; }

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

.sp-head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
}

/* Буква вместо аватарки: фото поставщика у нас нет, а пустой круг выглядит как незагрузка. */
.sp-ava {
  flex: 0 0 auto;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), #1e5fd0);
  color: #fff;
  font-size: 22px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.sp-head-text { min-width: 0; }

.sp-name {
  font-size: 19px;
  font-weight: 700;
  line-height: 1.25;
  word-break: break-word;
}

/* Подтверждение — плашкой, а не строкой текста: это метка доверия, её должно быть видно
   боковым зрением, до чтения. */
.sp-badge {
  display: inline-block;
  margin-top: 6px;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}

.sp-badge.is-ok { background: rgba(87, 199, 133, .16); color: #2f8f5b; }
.sp-badge.is-no { background: rgba(0, 0, 0, .06); color: var(--fg-dim); }

.sp-card {
  background: var(--card);
  border-radius: 14px;
  padding: 4px 14px;
  margin-bottom: 12px;
}

.sp-row {
  display: flex;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
  font-size: 15px;
}

.sp-row:last-child { border-bottom: none; }

.sp-label {
  flex: 0 0 88px;
  color: var(--fg-dim);
  font-size: 13px;
  padding-top: 1px;
}

.sp-value { flex: 1; min-width: 0; word-break: break-word; }

.sp-bio {
  padding: 14px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--fg-dim);
}
.sp-bio .sp-label { display: block; margin-bottom: 6px; font-weight: 700; color: var(--fg); }
.sp-last-seen { margin-top: 6px; color: var(--fg-dim); font-size: 13px; }
.sp-message-btn { width: 100%; margin-top: 14px; }

.sp-contacts { margin-top: 18px; }

/* Каждый способ связи — строка-действие: значок, что произойдёт, и сам контакт мелким.
   Раньше это были одинаковые синие ссылки, из которых не видно, откроется звонок или чат. */
/* a.sp-contact — звонок; button.sp-contact — остальное. Правила общие, чтобы ссылка и
   кнопка выглядели одинаково: разный тег здесь продиктован не оформлением, а тем, что
   переход по tel: из кода в Telegram блокируется, а по самой ссылке — нет. */
a.sp-contact { text-decoration: none; }

.sp-contact {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin-bottom: 10px;
  padding: 13px 14px;
  border: none;
  border-radius: 14px;
  background: var(--card);
  color: var(--fg);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

.sp-contact:active { transform: scale(.985); filter: brightness(.97); }

.sp-contact-ico {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
}

/* Цвет кружка кодирует способ связи: синий Telegram, зелёный звонок, серый маршрут —
   различить их можно, не читая подписи. */
.sp-tg  .sp-contact-ico { background: rgba(46, 124, 246, .14); }
.sp-tel .sp-contact-ico { background: rgba(87, 199, 133, .18); }

.sp-tg  .sp-contact-ico::before { content: "💬"; }
.sp-tel .sp-contact-ico::before { content: "📞"; }

.sp-contact-text { flex: 1; min-width: 0; }

.sp-contact-label {
  display: block;
  font-size: 15px;
  font-weight: 600;
}

.sp-contact-sub {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  color: var(--fg-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sp-contact-go {
  flex: 0 0 auto;
  color: var(--fg-dim);
  font-size: 20px;
  line-height: 1;
}

/* Строка «Назад + Статистика»: заголовок «Реклама на первом экране» убран как повтор, а
   отдельная строка под кнопкой съедала высоту, которая нужна графику. */
.pitch-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

/* Кнопка «Назад» несёт собственный отступ снизу (14px), и внутри этой строки он
   складывался с отступом самой строки - между «Статистикой» и карточкой графика зияла
   двойная щель. Внутри строки отступ кнопке не нужен: его задаёт сама строка. */
.pitch-topbar .back-btn { margin-bottom: 0; }

/* Сверху над строкой складывались ТРИ отступа: 12px у полосы статистики, 16px у main и
   4px у самого экрана - 32px пустоты на телефоне, где дорог каждый пиксель. Подрезаем
   отрицательным полем ТОЛЬКО на этом экране: main и полоса общие для всего приложения,
   трогать их значило бы поехать везде разом. */
.pitch-topbar { margin-top: -6px; }

.pitch-topbar-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg-dim);
}

/* Заголовок цены по центру: под ним плашка во всю ширину, и заголовок у левого края
   выглядел смещённым относительно неё. */
.pitch-price-heading { text-align: center; }

/* Блок цифр — по строке на число, поэтому нужен воздух между строками. */
/* Блок цифр читают слева направо: числа выстраиваются в столбик под своими подписями, и
   глазу не приходится искать начало каждой строки. Класс .earn-note центрирует текст и
   используется в десятках мест - переопределяем ТОЛЬКО здесь, сам класс не трогаем. */
#adPitchStats {
  line-height: 1.7;
  text-align: left;
  margin-left: 4px;
  margin-right: 4px;
}

/* Боковые поля карточки срезаны ради графика, поэтому текстовым строкам отступ возвращаем
   отдельно - иначе заголовок и легенда прилипают к краю. */
.chart-card > .chart-head,
.chart-card > .chart-legend,
.chart-card > .chart-help { padding-left: 8px; padding-right: 8px; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ГЛАВНЫЙ ЭКРАН - НОВОЕ ОФОРМЛЕНИЕ (Кирилл 2026-08-26)

   Тексты, состав и порядок блоков НЕ ТРОНУТЫ. Меняются только форма, цвет и движение.
   Правила дописаны в конец файла осознанно: так они перекрывают прежние без правки
   двадцати мест выше, и старое оформление можно вернуть, удалив один блок.

   1. Плитка асимметричная: угол срезан, радиусы по диагонали разные. Такую форму не даёт
      по умолчанию ни один конструктор - потому она и не читается шаблоном.
   2. Значок раздела вынесен огромным и обрезан краем: работает фоном, а не иконкой над
      подписью. Отдельная разметка не понадобилась - взят существующий .home-tile-icon.
   3. Каждый раздел в своём тоне. Одинаково-серая сетка - главный признак шаблона: она
      говорит, что все разделы равны, хотя это неправда.
   4. Нажатие: заливка выезжает из срезанного угла по диагонали, а не масштаб и не тень.
   ══════════════════════════════════════════════════════════════════════════════════════ */

:root {
  --pine: #0E6B5B;
  --pine-soft: #E4F0EC;
  /* Жёлтого в палитре больше нет (Кирилл 2026-08-27). Песочный тон читался как «выцвело»
     и спорил с зелёным - фирменным цветом приложения. Остальные тона приглушены: тон
     раздела должен ЕДВА отличать плитки друг от друга, а не раскрашивать экран. Насыщенность
     срезана примерно вдвое, оттенок у каждого сохранён - разделы по-прежнему различимы. */
  --clay: #A0705C;
  --clay-soft: #F2ECE9;
  --slate: #4C6180;
  --slate-soft: #ECEFF3;
  --plum-soft: #EFECF4;
  --mist-soft: #EAF0F3;
  --rose-soft: #F1EBEE;
  --stone-soft: #EEF0F2;
}

/* ── Баннер: срезанный угол и дышащая цветом полоса ────────────────────────────────────── */
.ad-banner {
  border-radius: 18px 4px 18px 4px;
  position: relative;
  overflow: hidden;
  padding-left: 20px;
}
.ad-banner::after {
  content: "";
  position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  /* Полоса была апельсин -> жёлтый: два самых кричащих цвета подряд на самом видном месте
     экрана. Теперь она в фирменном зелёном, от насыщенного к приглушённому. */
  background: linear-gradient(180deg, var(--pine), #6E9A8F);
  animation: ins-breathe 5s ease-in-out infinite;
}
@keyframes ins-breathe { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* ── Баланс ────────────────────────────────────────────────────────────────────────────── */
.home-balance-action {
  border-radius: 12px 3px 12px 3px;
  padding: 10px 15px;
  transition: transform .18s cubic-bezier(.2, .7, .3, 1), opacity .18s;
}
.home-balance-action:active { transform: translateY(1px) scale(.97); }
/* Была песочная с коричневым текстом - самое жёлтое пятно на главной. Нейтральная подложка
   с фирменным текстом: кнопка остаётся заметной, но не спорит с «Пополнить» рядом. */
.home-balance-action { background: var(--stone-soft); color: var(--pine); }
.home-balance-action.primary { background: var(--pine); color: #fff; }

/* ── Три быстрые кнопки ────────────────────────────────────────────────────────────────── */
/* ВНИМАНИЕ: это правило идёт ПОЗЖЕ базового (~строка 681) и перебивает его padding.
   Менять высоту кнопок надо ЗДЕСЬ - правка наверху ни на что не влияет. */
.quick-btn {
  position: relative;
  overflow: hidden;
  border: 1.5px solid var(--border);
  background: none;
  border-radius: 14px 3px 14px 3px;
  padding: 5px 6px;
  transition: border-color .25s, color .25s;
}
.quick-btn::after {
  content: "";
  position: absolute; left: 10px; right: 10px; bottom: 6px; height: 2px;
  background: var(--pine);
  transform: scaleX(0); transform-origin: left;
  transition: transform .4s cubic-bezier(.2, .7, .3, 1);
}
.quick-btn:active { border-color: var(--pine); color: var(--pine); opacity: 1; }
.quick-btn:active::after { transform: scaleX(1); }

/* Смайлик живёт сам по себе. У каждой кнопки СВОЙ сдвиг по времени: в такт они мигают
   гирляндой, вразнобой - выглядят живыми. */
.quick-emoji,
.quick-avatar {
  display: inline-block;
  animation: ins-sway 6s ease-in-out infinite;
  transition: transform .35s cubic-bezier(.2, .7, .3, 1);
}
.quick-btn:nth-child(2) .quick-emoji { animation-delay: -2s; }
.quick-btn:nth-child(3) .quick-emoji { animation-delay: -4s; }
.quick-btn:active .quick-emoji,
.quick-btn:active .quick-avatar { transform: scale(1.22) rotate(-8deg); }

@keyframes ins-sway {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  35%      { transform: translateY(-2.5px) rotate(-5deg); }
  70%      { transform: translateY(1px) rotate(4deg); }
}

/* ── Шесть разделов ───────────────────────────────────────────────────────────────────── */
.home-grid { gap: 9px; }

.home-tile {
  position: relative;
  overflow: hidden;
  justify-content: flex-end;      /* подпись прижата вниз, значок уходит вверх фоном */
  gap: 0;
  min-height: 116px;
  padding: 15px 14px 14px;
  border-radius: 18px 4px 18px 4px;
  /* Видимой плитка должна быть В БАЗЕ, а прятать её должна САМА анимация. Раньше было
     наоборот: opacity: 0 в базе и появление через forwards. Стоило скрипту снять
     отыгравшую анимацию - плитка возвращалась к базовому состоянию и ИСЧЕЗАЛА. */
  opacity: 1;
  animation: ins-rise .5s cubic-bezier(.2, .7, .3, 1) both;
}
@keyframes ins-rise {
  from { opacity: 0; transform: translateY(12px) scale(1); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.home-tile:nth-child(1) { animation-delay: .02s; }
.home-tile:nth-child(2) { animation-delay: .07s; }
.home-tile:nth-child(3) { animation-delay: .12s; }
.home-tile:nth-child(4) { animation-delay: .17s; }
.home-tile:nth-child(5) { animation-delay: .22s; }
.home-tile:nth-child(6) { animation-delay: .27s; }

/* Прежнее нажатие (масштаб всей плитки) отменяем - его делают все. Но НЕ через
   transform: none: тогда во время нажатия сбрасывалось бы и «дыхание». Держим текущий
   масштаб дыхания, который выставляет скрипт. */
.home-tile:active { transform: scale(var(--ins-s, 1)); opacity: 1; }

/* Дыхание: очень медленный переход, чтобы движение читалось как живое, а не как отклик
   на нажатие. Значения масштаба и яркости ставит скрипт - случайные и каждый раз разные,
   потому что любой набор ключевых кадров в CSS рано или поздно повторяется, и глаз это
   ловит. */
.home-tile {
  transform: scale(var(--ins-s, 1));
  filter: brightness(var(--ins-b, 1));
  transition: transform 2.6s cubic-bezier(.4, 0, .3, 1),
              filter 2.6s cubic-bezier(.4, 0, .3, 1);
}

/* Заливка из срезанного угла - подпись формы. Отдельный элемент не нужен: берём ::before. */
.home-tile::before {
  content: "";
  position: absolute; right: -20%; top: -60%; width: 160%; height: 160%;
  border-radius: 50%;
  transform: scale(0); transform-origin: top right;
  transition: transform .55s cubic-bezier(.2, .7, .3, 1);
  pointer-events: none;
}
.home-tile:active::before { transform: scale(1); }

/* Значок: тот же элемент, что был иконкой, — теперь фон. Разметку менять не пришлось. */
.home-tile-icon {
  position: absolute; right: -12px; top: -10px;
  font-size: 66px; line-height: 1;
  opacity: .16;
  pointer-events: none;
  transition: transform .45s cubic-bezier(.2, .7, .3, 1), opacity .3s;
  animation: ins-float 11s ease-in-out infinite;
  /* Отдельный слой. Без него бесконечное движение внутри обрезанной скруглённой плитки
     заставляет перерисовывать её текст каждый кадр - отсюда обрубленные буквы и цифры. */
  will-change: transform;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
.home-tile:nth-child(2) .home-tile-icon { animation-delay: -1.8s; }
.home-tile:nth-child(3) .home-tile-icon { animation-delay: -3.6s; }
.home-tile:nth-child(4) .home-tile-icon { animation-delay: -5.4s; }
.home-tile:nth-child(5) .home-tile-icon { animation-delay: -7.2s; }
.home-tile:nth-child(6) .home-tile-icon { animation-delay: -9s; }
.home-tile:active .home-tile-icon {
  transform: rotate(-4deg) scale(1.12) translate(-5px, 5px);
  opacity: .26;
}

@keyframes ins-float {
  0%, 100% { transform: rotate(8deg) translate(0, 0); }
  50%      { transform: rotate(3deg) translate(-5px, 5px); }
}

.home-tile-title {
  position: relative; z-index: 2;
  font-size: 15.5px; font-weight: 700; line-height: 1.25; letter-spacing: -.025em;
}

/* Тон по разделу - через существующий data-атрибут, разметку трогать не нужно. */
/* Тон - ПЕРЕМЕННАЯ, а не жёсткая заливка. Скрипт меняет её у случайных плиток, и переход
   сглаживает смену. Прибитый к разделу цвет сменить было бы нельзя, не переписывая правило.

   Переход длиннее самой паузы между сменами: цвет должен ПЕРЕТЕКАТЬ. Мгновенная смена
   читается как перерисовка экрана и выглядит сбоем, а не жизнью. */
.home-tile {
  background: var(--tint, var(--card));
  transition:
    background 1.6s cubic-bezier(.4, 0, .3, 1),
    transform 2.6s cubic-bezier(.4, 0, .3, 1),
    filter 2.6s cubic-bezier(.4, 0, .3, 1);
}

.home-tile::before { background: var(--tint-fill, rgba(20, 24, 28, .07)); }

/* Начальные тона. Дальше их подхватывает и перемешивает скрипт. */
.home-tile[data-home="feed"]       { --tint: var(--clay-soft);  --tint-fill: rgba(160, 112, 92, .10); }
.home-tile[data-home="form"]       { --tint: #E9F0ED;           --tint-fill: rgba(14, 107, 91, .10); }
.home-tile[data-home="news"]       { --tint: var(--slate-soft); --tint-fill: rgba(76, 97, 128, .10); }
.home-tile[data-home="tools"]      { --tint: var(--plum-soft);  --tint-fill: rgba(107, 78, 155, .10); }
.home-tile[data-home="broadcasts"] { --tint: var(--mist-soft);  --tint-fill: rgba(56, 122, 156, .10); }
.home-tile[data-home="projects"]   { --tint: var(--rose-soft);  --tint-fill: rgba(150, 100, 120, .09); }

/* ── Три кнопки снизу ─────────────────────────────────────────────────────────────────── */
.home-idea-btn {
  border-radius: 14px 3px 14px 3px;
  border: 1.5px dashed var(--border);
  transition: border-color .25s, color .25s;
}
.home-idea-btn:active { border-color: var(--pine); color: var(--pine); }

.home-earn-btn {
  position: relative;
  overflow: hidden;
  border-radius: 18px 4px 18px 4px;
  background: var(--pine);
}
/* Блик раз в несколько секунд - ЕДИНСТВЕННОЕ громкое движение на экране. Если добавить
   такое всюду, получится ярмарка, а не дорогое приложение. */
.home-earn-btn::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 0; width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .22), transparent);
  transform: translateX(-133.33%) skewX(-18deg);
  animation: ins-shine 5.5s ease-in-out infinite;
  /* Свой слой у блика: иначе браузер каждый кадр перерисовывает кнопку целиком вместе с
     надписью, и на телефоне буквы режутся посреди строки. */
  will-change: transform;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
/* Двигаем transform, а не left. left - это раскладка: её изменение заставляет браузер
   пересчитывать и перерисовывать содержимое кнопки 60 раз в секунду. transform живёт на
   видеокарте и текста не касается. Картинка та же, цена другая. */
@keyframes ins-shine {
  0%   { transform: translateX(-133.33%) skewX(-18deg); }
  45%  { transform: translateX(266.67%) skewX(-18deg); }
  100% { transform: translateX(266.67%) skewX(-18deg); }
}

.admin-entry-btn {
  border-radius: 14px 3px 14px 3px;
  border: 1.5px dashed var(--border);
}

@media (prefers-reduced-motion: reduce) {
  .home-tile { opacity: 1; transform: none; animation: none; }
  .ad-banner::after,
  .home-earn-btn::after,
  .home-tile-icon,
  .quick-emoji,
  .quick-avatar { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ЖИВОЙ ФОН (Кирилл 2026-08-26)

   Фон был анимирован и раньше, но двумя СИНИМИ пятнами от прежней палитры - на белом их
   практически не видно, и к новым цветам они не подходили. Заменяем.

   Два независимых слоя пятен в цветах разделов: хвойный, абрикос, песок на одном; слива и
   сланец на другом. Периоды 34 и 47 секунд - НАМЕРЕННО не кратные: при кратных слои
   сходятся в исходное положение регулярно, глаз ловит петлю и фон начинает раздражать.
   При некратных рисунок не повторяется часами.

   ПОЧЕМУ ОТДЕЛЬНЫМ СЛОЕМ, а не фоном body. Анимация background-position заставляет
   браузер перерисовывать страницу целиком на каждом кадре - на телефоне это заметно по
   рывкам при прокрутке и по батарее. Здесь двигается только transform закреплённого
   слоя: он живёт на своём слое видеокарты и содержимого не касается.

   Прозрачность 16-20%. Первая версия была на 7-9% - её попросту не замечали. Выше 20% я
   бы не поднимался: карточки лежат на фоне, и при более плотных пятнах белый край плитки
   начинает спорить с подложкой, а экран выглядеть грязным.
   ══════════════════════════════════════════════════════════════════════════════════════ */

body {
  /* Старая двухпятновая анимация выключена - её заменяет слой ниже. Снимаем и
     background-size: без него правило bg-drift осталось бы висеть вхолостую и заставляло
     бы браузер держать у страницы фоновый слой, который уже не используется. */
  background-image: none;
  background-size: auto;
  animation: none;
  position: relative;
}

/* ЗАМША. Настоящая замша читается двумя вещами: мелким ворсом и тем, как по нему ходит
   свет, когда ворс приминается. Поэтому слоя три:
   1. цвет - мягкие пятна палитры (ниже);
   2. ворс - тонкое зерно, нарисованное фильтром турбулентности прямо в стилях. Это не
      картинка с диска: ничего не грузится, вес нулевой, на любом экране не мылится;
   3. блик - широкая мягкая полоса, медленно идущая по диагонали. Она и создаёт ощущение,
      что поверхность шевелится, а не просто переливается.  */
/* Постоянное притенение по ВСЕМУ периметру. Неподвижное - это основа, на которой уже
   играют вспышки. Середина остаётся чисто белой: белое поле в центре работает подсветкой
   для содержимого, и кнопки на нём читаются лучше всего. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(38, 44, 54, .13) 0%, transparent 17%),
    linear-gradient(0deg,   rgba(38, 44, 54, .13) 0%, transparent 17%),
    linear-gradient(90deg,  rgba(38, 44, 54, .12) 0%, transparent 19%),
    linear-gradient(270deg, rgba(38, 44, 54, .12) 0%, transparent 19%);
}

/* Ворс: отдельным неподвижным слоем поверх цвета. Двигать зерно нельзя - оно начнёт
   рябить и резать глаз; замша шевелится светом, а не ворсинками. */
/* ══════════════════════════════════════════════════════════════════════════════════════
   МАТОВЫЙ СЕРЫЙ ФОН (Кирилл 2026-08-26)

   Подложка перестала быть белой. Серый выбран ХОЛОДНЫЙ и приглушённый: тёплый серый на
   экране всегда отдаёт желтизной и выглядит несвежим, а насыщенный - грязным.

   ГЛАВНОЕ ОГРАНИЧЕНИЕ: карточки в приложении светло-серые (#f4f4f6), их используют 25
   элементов. Сделай фон такой же светлоты - и они исчезнут. Поэтому подложка заметно
   темнее карточек: разница в светлоте и создаёт ощущение, что карточки лежат ПОВЕРХ
   поверхности, а не нарисованы на ней. Отсюда и объём, без единой тени.

   Матовость даёт зерно: глянец на экране получается бликом по всей плоскости, матовость -
   мелкой шероховатостью, которая этот блик рассеивает. Поэтому зерно плотнее, чем было,
   а блик - узкий и мягкий по краям.
   ══════════════════════════════════════════════════════════════════════════════════════ */
body {
  /* Возврат к белому: серая подложка глушила карточки и весь экран становился тусклым.
     Объём теперь даёт не фон, а ТЕНЬ ПОД КАРТОЧКАМИ, которая ходит за наклоном телефона. */
  background-color: var(--bg);
}

/* Виньетка убрана: на белом она давала грязный ореол по краям, ради которого и затевался
   серый. Объём теперь строится тенью под карточками, а не затемнением фона. */

.ins-grain {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* Плотнее прежнего: именно зерно даёт матовость. На глянце свет отражается всей
     плоскостью, на матовой поверхности его рассеивает шероховатость. */
  /* На белом плотное зерно читается не как матовость, а как грязь на экране. Оставляем
     едва различимый след - он снимает «пластиковость» ровной заливки. */
  opacity: .16;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* Блик по ворсу: очень широкий, очень медленный, идёт по диагонали и меняет направление.
   Период 29 секунд - не кратен ни 34, ни 47 у цветных слоёв, поэтому три движения никогда
   не сходятся в одну точку и рисунок не зацикливается. */
.ins-sheen {
  position: fixed;
  inset: -40%;
  z-index: -1;
  pointer-events: none;
  /* Узкая полоса с мягкими краями - свет, скользящий по матовой поверхности. Резкая
     граница дала бы глянец, а нам нужен рассеянный отблеск. */
  background: linear-gradient(115deg,
    transparent 36%,
    rgba(255, 255, 255, .42) 47%,
    rgba(255, 255, 255, .60) 50%,
    rgba(255, 255, 255, .42) 53%,
    transparent 64%);
  animation: ins-sheen-move 23s ease-in-out infinite alternate;
}

/* ВСПЫШКИ. Мягкие пятна света, распускающиеся и гаснущие в СЛУЧАЙНЫХ местах по всей
   площади экрана. Положение, размер, цвет, длительность и пауза - каждый раз новые, их
   ставит скрипт: на ключевых кадрах CSS вспышки повторялись бы по кругу, и через минуту
   глаз ловит, где загорится следующая.

   Появление и угасание намеренно долгие (6-11 секунд): вспышка должна ощущаться сменой
   освещения в комнате, а не миганием. Быстрая вспышка на фоне - это уже реклама. */
.ins-bloom {
  position: fixed;
  z-index: -1;
  pointer-events: none;
  border-radius: 50%;
  opacity: 0;
  will-change: opacity, transform;
}

@keyframes ins-sheen-move {
  from { transform: translate3d(-22%, -14%, 0); }
  to   { transform: translate3d(22%, 14%, 0); }
}

/* Второй слой с другим набором пятен и другим периодом: два независимых слоя дают
   переливание, которого одним не добиться - пятна то расходятся, то накладываются. */
/* Второй цветной слой и его движение убраны: фон стал белым, двигать на нём нечего.
   Всё движение теперь в вспышках по периметру - они и дают жизнь. */
/* Шапка и карточки остаются НЕПРОЗРАЧНЫМИ: цветной фон под текстом - первое, что делает
   интерфейс дешёвым и нечитаемым. Фон виден в промежутках, и этого достаточно. */
.topbar { background: var(--bg); }

@media (prefers-reduced-motion: reduce) {
  body::before,
  body::after,
  .ins-sheen { animation: none; }
  .ins-bloom { display: none; }
  /* Виньетку оставляем: она статична и даёт объём без движения. */
}

/* ── Заголовок в одну строку с «Назад» ──────────────────────────────────────────────────
   Экономит две строки высоты на каждом экране. В строке заголовок становится скромнее:
   в столбик он был главным, а рядом с кнопкой обязан уступить ей - иначе спорят два
   тяжёлых элемента и строка выглядит перегруженной. */
.screen-topbar {
  margin-bottom: 10px;
}

.screen-topbar .back-btn { margin: 0; flex: 0 0 auto; }
/* Действие прижато к ПРАВОМУ краю, вплотную к значку ссылки. Раньше строка
   растягивала промежутки поровну, и кнопка зависала посреди пустоты - между
   «Назад» и значком, не примыкая ни к чему. Правая сторона строки должна
   читаться как одна группа. */
.screen-topbar .topbar-action { margin-left: auto; flex: 0 0 auto; }
.screen-topbar > .share-btn { flex: 0 0 auto; }

.screen-topbar > h1,
.screen-topbar > h2,
.screen-topbar > h3 {
  margin: 0;
  text-align: right;
  font-size: 15px;
  font-weight: 600;
  color: var(--fg-dim);
  letter-spacing: -.01em;
  /* Длинные названия не должны ломать строку: обрезаем многоточием, кнопка важнее. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   Боковые полосы убраны: их работу теперь делают вспышки по периметру, и вместе они
   спорили - полоса тянула край постоянно, вспышка приходила поверх, и получалось два
   разных затемнения на одном месте.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* Тени под карточками - обычные, от света сверху. Никаких датчиков. */
.home-tile {
  box-shadow:
    0 2px 6px rgba(20, 24, 28, .07),
    0 8px 20px rgba(20, 24, 28, .08),
    inset 0 -1px 0 rgba(255, 255, 255, .55);
}

.quick-btn {
  box-shadow: 0 1px 4px rgba(20, 24, 28, .05), 0 4px 12px rgba(20, 24, 28, .05);
}

.home-earn-btn {
  box-shadow: 0 3px 8px rgba(14, 107, 91, .22), 0 10px 24px rgba(14, 107, 91, .18);
}

.ad-banner {
  box-shadow: 0 4px 12px rgba(20, 24, 28, .14), 0 12px 30px rgba(20, 24, 28, .10);
}

@media (prefers-reduced-motion: reduce) {
  .ins-edge { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ЕДИНЫЙ СТИЛЬ ДЛЯ ВСЕХ КАРТОЧЕК ПРИЛОЖЕНИЯ (Кирилл 2026-08-26)

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

   Класс .earn-card общий и встречается 76 раз, поэтому одна правка приводит к единому
   виду весь список сразу, без правки разметки.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.earn-card {
  position: relative;
  overflow: hidden;
  /* Та же асимметрия, что у плиток главного экрана: один угол срезан. */
  border-radius: 18px 4px 18px 4px;
  box-shadow:
    0 2px 6px rgba(20, 24, 28, .06),
    0 8px 20px rgba(20, 24, 28, .07),
    inset 0 -1px 0 rgba(255, 255, 255, .6);
}

/* Заливка из срезанного угла - тот же отклик на нажатие, что на главной. */
.earn-card-action::before {
  content: "";
  position: absolute; right: -20%; top: -60%; width: 160%; height: 160%;
  border-radius: 50%;
  background: rgba(14, 107, 91, .10);
  transform: scale(0); transform-origin: top right;
  transition: transform .55s cubic-bezier(.2, .7, .3, 1);
  pointer-events: none;
}
.earn-card-action:active::before { transform: scale(1); }
.earn-card-action:active { opacity: 1; }

/* Знак карточки: тот же приём, что на главной - огромный, обрезан краем, работает фоном.
   Ставится скриптом из смайлика в заголовке, поэтому разметку править не пришлось. */
.card-mark {
  position: absolute; right: -12px; top: -10px;
  font-size: 60px; line-height: 1;
  opacity: .13;
  pointer-events: none;
  animation: ins-float 11s ease-in-out infinite;
  /* То же, что у плиток главной: движение живёт своим слоем и не трогает цифры рядом. */
  will-change: transform;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
.earn-card:nth-child(2n) .card-mark { animation-delay: -3.7s; }
.earn-card:nth-child(3n) .card-mark { animation-delay: -7.4s; }

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

   Слой снимаем ВМЕСТЕ с анимацией, и это главное. will-change: transform держит у элемента
   собственный слой композиции навсегда - даже когда двигаться уже нечему. Внутри карточки
   с overflow:hidden и скруглением такой мёртвый слой заставлял iOS не дорисовывать текст
   рядом: счётчик показывал «0» вместо «0 товаров», а любое касание или прокрутка чинили
   картинку, потому что вызывали перерисовку. Слой без движения не ускоряет ничего, только
   занимает память и ломает соседей. */
.admin-menu .card-mark,
.profile-tile .card-mark,
.card-mark-still {
  animation: none;
  will-change: auto;
  -webkit-backface-visibility: visible;
  backface-visibility: visible;
}

/* Текст держим БЕЗ собственного слоя. Свой слой тут вреден: значение счётчика меняется
   скриптом уже после отрисовки, а iOS при смене текста в готовом слое иногда оставляет
   прежнее содержимое - прочерк "-" из разметки превращался в тёмную полосу вместо слова.
   Слои нужны анимированным украшениям, а не тексту, который меняется. */
.earn-card-title,
.earn-card-amount,
.earn-card-desc { position: relative; z-index: 2; }

/* Числа - хвойным вместо синего: на главной уже нет ни одного синего элемента, а здесь
   они оставались главным цветом и тянули экран в старую палитру. */
.earn-card-amount { color: var(--pine); }

/* Ссылки-проекты и прочие плитки - по тем же законам. */
.project-link,
.tile {
  border-radius: 18px 4px 18px 4px;
  box-shadow:
    0 2px 6px rgba(20, 24, 28, .06),
    0 8px 20px rgba(20, 24, 28, .07);
}

/* Кнопки действий: главная - хвойная, как «Зарабатывай с нами» на первом экране. */
.submit-btn,
.topup-btn {
  border-radius: 16px 4px 16px 4px;
  background: var(--pine);
  box-shadow: 0 3px 8px rgba(14, 107, 91, .20), 0 10px 24px rgba(14, 107, 91, .14);
}

.back-btn { border-radius: 999px; }

.admin-approve-btn,
.admin-reject-btn { border-radius: 12px 3px 12px 3px; }

/* Карандаш правки поста: только владельцу размещения и админу. Лежит поверх баннера в
   углу - отдельной строкой он занял бы место, а внутрь кнопки его класть нельзя. */
.ad-banner-wrap { position: relative; }

.ad-banner-edit {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 3;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 12px 3px 12px 3px;
  /* Подложка почти прозрачная. Прежняя плашка была видна как отдельный прямоугольник,
     наклеенный поверх баннера, - кнопка должна принадлежать баннеру, а не лежать на нём. */
  background: rgba(255, 255, 255, .07);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: background .2s, transform .18s cubic-bezier(.2, .7, .3, 1);
}

/* Значок - контурный, а не эмодзи. Системный смайлик нельзя ни перекрасить, ни провести им
   линию: он картинка. Контур живёт теми же правилами, что остальной интерфейс, и умеет
   рисовать - на этом и держится вся анимация. */
.ad-banner-edit { display: flex; align-items: center; justify-content: center; }

.ad-banner-edit:active { background: rgba(255, 255, 255, .16); transform: scale(.96); }

/* ── Карандаш ведёт линию ────────────────────────────────────────────────────────────────
   Прежний вариант был эмодзи, который дёргался, и белая полоска, которая выезжала рядом.
   Дёрганье само по себе читается дёшево: настоящий карандаш не трясётся, он ведёт.

   Здесь линия ПРОРИСОВЫВАЕТСЯ - тем же приёмом, что подпись на бумаге: штрих задан пунктиром
   в длину всего пути и сдвинут за его конец, а движение возвращает сдвиг к нулю, и штрих
   проступает от начала к концу. Карандаш идёт вдоль неё и к концу возвращается на место,
   а линия тает - остаётся впечатление, что он только что расписался, а не что кнопка мигнула.

   Всё идёт ПРИ НАВЕДЕНИИ (Кирилл 2026-08-27): нажатие - уже решение, показывать что-то
   после него поздно, следом открывается окно и накрывает экран. */
.pencil-ico {
  width: 19px;
  height: 20px;
  display: block;
  overflow: visible;
}

.pencil-ico .pen-body,
.pencil-ico .pen-line {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.pencil-ico .pen-body {
  stroke-width: 1.7;
  opacity: .68;
  /* Поворот вокруг грифеля, а не середины. В SVG для этого обязателен transform-box:
     fill-box - без него отсчёт идёт от холста всего документа, и значок улетает. */
  transform-box: fill-box;
  transform-origin: 22% 84%;
  transition: opacity .25s;
}

/* Линия невидима в покое: она след, а не часть значка. 21 - НАСТОЯЩАЯ длина зигзага,
   посчитанная по его сегментам (четыре по 4 на 3.4 пикселя). Взять число на глаз нельзя:
   меньше длины - хвост штриха останется висеть, заметно больше - прорисовка начнётся не
   сразу, а после паузы, и движение будет казаться запаздывающим. */
.pencil-ico .pen-line {
  stroke-width: 1.9;
  opacity: 0;
  stroke-dasharray: 21;
  stroke-dashoffset: 21;
}

/* Кнопка приподнимается ПЛАВНО, без отскока: подпрыгивающая кнопка и есть та самая
   дешевизна. Работает переход, а не ключевые кадры. */
.ad-banner-edit:hover,
.ad-photo-edit:hover,
.ad-banner-edit.drawing,
.ad-photo-edit.drawing {
  background: rgba(255, 255, 255, .17);
  transform: scale(1.06);
}

.ad-banner-edit:hover .pen-body,
.ad-photo-edit:hover .pen-body,
.ad-banner-edit.drawing .pen-body,
.ad-photo-edit.drawing .pen-body {
  opacity: 1;
  animation: pen-glide 1.05s cubic-bezier(.35, .1, .2, 1) both;
}

.ad-banner-edit:hover .pen-line,
.ad-photo-edit:hover .pen-line,
.ad-banner-edit.drawing .pen-line,
.ad-photo-edit.drawing .pen-line {
  animation: pen-draw 1.05s cubic-bezier(.35, .1, .2, 1) both;
}

/* Карандаш опускается к линии, ведёт её слева направо и возвращается в исходное. Линия -
   зигзаг, поэтому грифель на ходу поднимается и опускается: три колебания на четыре
   сегмента. Без них он ехал бы ровно, а под ним рисовалась пила - и сразу было бы видно,
   что это две несвязанные анимации, а не один жест.

   Вершины расставлены по времени так же, как излом по длине пути: к 62% карандаш доходит
   до правого края ровно тогда, когда штрих дорисован, и только после этого идёт назад.

   Наклон меняется на девять градусов - ровно столько даёт живая рука, не больше. */
@keyframes pen-glide {
  0%   { transform: translate(-3.6px, 2.4px) rotate(-8deg); }
  14%  { transform: translate(-3.2px, 3.6px) rotate(-9deg); }
  26%  { transform: translate(-1.6px, 1.8px) rotate(-8deg); }
  38%  { transform: translate(0.4px, 3.6px) rotate(-7deg); }
  50%  { transform: translate(2.4px, 1.8px) rotate(-5deg); }
  62%  { transform: translate(4.4px, 3.6px) rotate(-4deg); }
  100% { transform: translate(0px, 0px) rotate(0deg); }
}

/* Штрих проступает за грифелем и гаснет позже него: сначала он есть, потом карандаш уходит,
   и только затем след тает. Обратный порядок читался бы как стирание. */
@keyframes pen-draw {
  0%   { stroke-dashoffset: 21; opacity: 0; }
  16%  { stroke-dashoffset: 19; opacity: .9; }
  62%  { stroke-dashoffset: 0;  opacity: .9; }
  82%  { stroke-dashoffset: 0;  opacity: .9; }
  100% { stroke-dashoffset: 0;  opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ad-banner-edit:hover .pen-body,
  .ad-banner-edit:hover .pen-line,
  .ad-photo-edit:hover .pen-body,
  .ad-photo-edit:hover .pen-line,
  .ad-banner-edit.drawing .pen-body,
  .ad-banner-edit.drawing .pen-line,
  .ad-photo-edit.drawing .pen-body,
  .ad-photo-edit.drawing .pen-line { animation: none; }
}

/* Окно обрезки ВЫШЕ остальных окон. Оно единственное открывается ПОВЕРХ другого окна -
   правки поста, - а слой у них был общий (30). Обрезка открывалась ЗА правкой, и человек
   видел, что нажатие не делает ничего. Именно это Кирилл и поймал. */
#cropOverlay { z-index: 90; }

/* Рамка выбора фото для баннера. Пунктиром и в пропорциях самого баннера: человек должен
   видеть, что именно попадёт на первый экран, ещё до загрузки. */
.ad-photo-frame {
  position: relative;
  margin-top: 6px;
  width: 100%;
  aspect-ratio: 3.2 / 1;
  border: 2px dashed var(--border);
  border-radius: 18px 4px 18px 4px;
  background: var(--card);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.ad-photo-frame:active { border-color: var(--pine); }

.ad-photo-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ad-photo-hint {
  position: relative;
  z-index: 2;
  font-size: 13px;
  color: var(--fg-dim);
}

.ad-photo-clear { margin-top: 8px; }

/* ── Карандаш на примере ─────────────────────────────────────────────────────────────────
   Тот же угол и та же форма, что у карандаша на настоящем баннере: одно действие - один
   знак, в двух местах он не имеет права выглядеть по-разному.

   Курсор меняется только у него: сама рамка с фотографией больше не кнопка, и рука над ней
   обещала бы нажатие, которого нет. */
.ad-photo-edit {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 3;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: 10px 3px 10px 3px;
  background: rgba(255, 255, 255, .1);
  color: #fff;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: background .2s, transform .18s cubic-bezier(.2, .7, .3, 1);
}

.ad-photo-edit { display: flex; align-items: center; justify-content: center; }

.ad-photo-edit:active { background: rgba(255, 255, 255, .28); transform: scale(.96); }

/* Рамка с фотографией больше не нажимается - и не притворяется нажимаемой. Пустая
   по-прежнему кнопка: там написано, что делать, и мишень обязана совпадать с надписью. */
.ad-photo-frame:has(.ad-photo-edit:not(.hidden)) { cursor: default; }

/* Полоса загрузки фото. Тонкая, по нижнему краю рамки - там она не спорит с самим фото и
   не сдвигает разметку, появляясь и исчезая. */
.ad-photo-bar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 3;
  height: 3px;
  background: rgba(20, 24, 28, .10);
  overflow: hidden;
}

.ad-photo-bar i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--pine);
  /* Медленный переход намеренно: полоса ползёт плавно, а не прыгает рывками между
     редкими отметками прогресса. */
  transition: width .5s cubic-bezier(.3, .8, .4, 1);
}

/* ── Фото на баннере: занимает всю кнопку ────────────────────────────────────────────────
   У баннера слева отступ 20px под цветную полосу, а фон по умолчанию рисуется ВНУТРИ
   отступов: снимок оказывался сдвинут вправо на 20 пикселей и срезан слева - это Кирилл и
   увидел.

   Правило дописано в САМЫЙ КОНЕЦ файла намеренно. Специфичность у .ad-banner-photo и
   .ad-banner одинаковая, поэтому побеждает то, что стоит ниже. Первая попытка стояла
   выше правила с отступом и молча перебивалась - проверка это и показала. */
.ad-banner-photo {
  background-origin: border-box;
  background-clip: border-box;
  padding-left: 0;
}

/* Отступ под полосу возвращаем ТЕКСТУ, а не всей кнопке - иначе он снова съест фото. */
.ad-banner-photo .ad-banner-body { padding-left: 20px; }

/* ГЛАВНОЕ. Класс .ad-banner-sold ставится ВМЕСТЕ с фото и задаёт сокращённое свойство
   background - а оно сбрасывает background-size в auto. Картинка рисовалась в натуральную
   величину и выходила увеличенным обрывком: именно это Кирилл и видел.

   Файл при этом был правильный - замер показал ровно 900×281, то есть 3.2:1. Дело было не
   в обрезке и не в Telegram, а в одной строке стилей.
   
   Повторяем размер и раскладку ПОСЛЕ всех правил, где встречается сокращённый background. */
.ad-banner.ad-banner-photo {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-origin: border-box;
  background-clip: border-box;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ОФОРМЛЕНИЕ РЕКЛАМНОГО ПОСТА

   Шрифт, движение и размытие выбирает сам рекламодатель. Ни одного загружаемого шрифта:
   на рынке связь слабая, а баннер стоит первым экраном - лишний запрос задержал бы его
   показ ради начертания. Все четыре варианта собраны из системных наборов, поэтому
   рисуются мгновенно и выглядят родными для устройства.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.ad-banner-photo .ad-banner-body {
  /* Текст ВНИЗ (Кирилл 2026-08-27). Наверху он накрывал середину логотипа - самую заметную
     его часть; внизу подпись лежит под изображением, как подпись и должна. */
  align-items: flex-start;
  justify-content: flex-end;
  text-align: left;
  height: 100%;
  display: flex;
  flex-direction: column;
}

/* Затемнение идёт следом за текстом: плотное внизу, прозрачное сверху - иначе притеняло бы
   пустое место, а текст оставался на светлом.

   Силу задаёт рекламодатель ползунком: --ad-dim это ПЛОТНОСТЬ У НИЖНЕГО КРАЯ (0..0.9), а
   верх и середина считаются от неё долями. Фиксированные значения не годились: на светлом
   логотипе текст тонул без затемнения, а на тёмном снимке то же затемнение превращало
   баннер в чёрный прямоугольник. */
.ad-banner-photo .ad-banner-body {
  background: linear-gradient(180deg,
    rgba(10, 16, 26, calc(var(--ad-dim, .8) * .14)) 0%,
    rgba(10, 16, 26, calc(var(--ad-dim, .8) * .48)) 46%,
    rgba(10, 16, 26, var(--ad-dim, .8)) 100%);
}

/* Заголовок крупнее и слева. Прежний размер терялся на фотографии: текст обязан спорить с
   картинкой, иначе пост читается как картинка без подписи. */
.ad-banner-photo .ad-banner-title {
  display: block;
  font-size: var(--ad-title-size, 26px);
  line-height: 1.05;
  letter-spacing: var(--ad-track, -.03em);
}

.ad-banner-photo .ad-banner-sub {
  display: block;
  margin-top: 6px;
  /* Подпись считается от заголовка, а не задаётся отдельно: два независимых размера
     разъезжаются, и на крупном заголовке подпись выглядит случайно забытой. */
  font-size: calc(var(--ad-title-size, 26px) * .52);
  letter-spacing: calc(var(--ad-track, 0em) * .6);
  /* НЕПРОЗРАЧНАЯ, и это принципиально (Кирилл 2026-08-27). Здесь стояло opacity: .88 -
     а прозрачность применяется к самим буквам, не к фону под ними. Затемнение просвечивало
     СКВОЗЬ подпись: тянешь ползунок затемнения, и буквы уходят в синеву вместе с фотографией.
     Ровно это и было видно.
     Цвет тоже свой: общий .ad-banner-sub задаёт #9fb3d0 - голубовато-серый, он хорош на
     тёмной плашке без фото, но поверх снимка читается как выцветший. */
  color: #F2F5F9;
  opacity: 1;
}

/* ── Шрифты ──────────────────────────────────────────────────────────────────────────────
   Шестнадцать семейств, названных своими именами. Ни одно не загружается: баннер стоит
   первым экраном, и лишний запрос за начертанием задержал бы его показ на слабой связи
   рынка. Поэтому у каждого - стопка запасных: на iPhone встанет первое, на Android
   ближайшее похожее, и в худшем случае системное. Начертание, регистр, размер и разрядка
   вынесены в ОТДЕЛЬНЫЕ настройки: раньше они были вшиты в пресет, и «крупный» означал
   одновременно и шрифт, и жирность, и заглавные - поменять что-то одно было нельзя. */
.ad-font-helvetica   .ad-banner-title, .ad-font-helvetica   .ad-banner-sub { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
.ad-font-avenir      .ad-banner-title, .ad-font-avenir      .ad-banner-sub { font-family: "Avenir Next", Avenir, "Segoe UI", Roboto, sans-serif; }
.ad-font-futura      .ad-banner-title, .ad-font-futura      .ad-banner-sub { font-family: Futura, "Trebuchet MS", "Century Gothic", sans-serif; }
.ad-font-optima      .ad-banner-title, .ad-font-optima      .ad-banner-sub { font-family: Optima, Candara, "Gill Sans", sans-serif; }
.ad-font-verdana     .ad-banner-title, .ad-font-verdana     .ad-banner-sub { font-family: Verdana, Tahoma, sans-serif; }
.ad-font-trebuchet   .ad-banner-title, .ad-font-trebuchet   .ad-banner-sub { font-family: "Trebuchet MS", Tahoma, sans-serif; }
.ad-font-georgia     .ad-banner-title, .ad-font-georgia     .ad-banner-sub { font-family: Georgia, "Times New Roman", serif; }
.ad-font-times       .ad-banner-title, .ad-font-times       .ad-banner-sub { font-family: "Times New Roman", Times, serif; }
.ad-font-didot       .ad-banner-title, .ad-font-didot       .ad-banner-sub { font-family: Didot, "Bodoni MT", Georgia, serif; }
.ad-font-baskerville .ad-banner-title, .ad-font-baskerville .ad-banner-sub { font-family: Baskerville, "Palatino Linotype", Palatino, serif; }
.ad-font-palatino    .ad-banner-title, .ad-font-palatino    .ad-banner-sub { font-family: Palatino, "Palatino Linotype", "Book Antiqua", serif; }
.ad-font-copperplate .ad-banner-title, .ad-font-copperplate .ad-banner-sub { font-family: Copperplate, "Copperplate Gothic Light", Optima, sans-serif; }
.ad-font-typewriter  .ad-banner-title, .ad-font-typewriter  .ad-banner-sub { font-family: "American Typewriter", "Courier New", Courier, monospace; }
.ad-font-menlo       .ad-banner-title, .ad-font-menlo       .ad-banner-sub { font-family: Menlo, Consolas, "Courier New", monospace; }
.ad-font-marker      .ad-banner-title, .ad-font-marker      .ad-banner-sub { font-family: "Marker Felt", "Comic Sans MS", cursive; }
.ad-font-snell       .ad-banner-title, .ad-font-snell       .ad-banner-sub { font-family: "Snell Roundhand", "Brush Script MT", cursive; }

/* ── Насыщенность и регистр ──────────────────────────────────────────────────────────── */
/* Трёх ступеней достаточно, и все три РЕАЛЬНО есть у системных шрифтов. Четвёртой,
   «очень жирной», больше нет: начертания Black у Helvetica Neue, Georgia и почти всех
   остальных не существует, и её приходилось подделывать обводкой - выходила окантовка,
   а не жирный текст. Настройка, которая не может сработать, хуже её отсутствия.
   Регистр тоже убран: как ввели, так и показываем. */
/* «Обычная» = 400, а не 500 (Кирилл 2026-08-28). Начертание Medium есть у единиц - у
   Helvetica Neue и Avenir, - а у Georgia, Didot, Verdana, Times, Menlo его нет, и 500
   отдавало тот же Regular, что и 300. Выходило, что «тонкая» и «обычная» рисуют одно и то
   же: ровно это Кирилл и увидел на Didot.

   400 есть у КАЖДОГО шрифта - это и делает «обычную» настоящей опорной точкой. 700 тоже
   работает всегда: если жирного начертания нет, браузер утолщает сам. А вот 300 есть далеко
   не у всех, и там, где его нет, кнопка «Тонкая» гасится - проверка идёт замером, см.
   fontHasLight в app.js. */
.ad-w-light  .ad-banner-title { font-weight: 300; }
.ad-w-normal .ad-banner-title { font-weight: 400; }
.ad-w-bold   .ad-banner-title { font-weight: 700; }
.ad-w-light  .ad-banner-sub   { font-weight: 300; }
.ad-w-normal .ad-banner-sub   { font-weight: 400; }
.ad-w-bold   .ad-banner-sub   { font-weight: 600; }

/* ── Размытие фотографии ─────────────────────────────────────────────────────────────────
   Размывается ОТДЕЛЬНЫЙ слой, а не сама кнопка: иначе размылся бы и текст поверх - ровно
   то, ради чего размытие и делается, перестало бы работать. */
.ad-banner-photo { background-image: none !important; }

.ad-banner-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: inherit;
}

/* Размытие теперь плавное, а не три ступени. --ad-blur это готовое число пикселей, его
   считает и ставит сам скрипт.

   --ad-bg-scale обязателен: размытие съедает края слоя и по периметру появляется светлая
   кайма. Слой увеличивают ровно настолько, чтобы кайма ушла за границы баннера. Масштаб
   держим ОТДЕЛЬНОЙ переменной, а не жёстким числом, потому что движения «Наплыв» и
   «Панорама» тоже правят transform и обязаны отталкиваться от него - иначе включённое
   движение сбрасывало бы поправку и кайма возвращалась. */
.ad-banner-bg {
  filter: blur(calc(var(--ad-blur, 0) * 1px));
  transform: scale(var(--ad-bg-scale, 1));
}

.ad-banner-photo .ad-banner-body { position: relative; z-index: 2; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ДВИЖЕНИЕ. Разделено на два независимых набора (Кирилл 2026-08-27): буквы отдельно, фон
   отдельно. Прежний общий список заставлял выбирать между «текст появляется» и «снимок
   плывёт», хотя это разные слои и мешать друг другу им нечем - теперь можно и то и другое
   разом.

   Длительности НЕ записаны числами: все кратны одной единице --ad-t, которую двигает
   ползунок скорости. Иначе «быстрее» пришлось бы прописывать двадцать раз и однажды где-то
   забыть. Умножение на безразмерное число - самый совместимый вид записи, деление времени
   на переменную местами не поддерживается.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── Движение букв ─────────────────────────────────────────────────────────────────────── */

.ad-tanim-fade .ad-banner-body { animation: ad-fade-in calc(var(--ad-t, 1s) * 1) ease-out both; }
@keyframes ad-fade-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* Подъём: заголовок и подпись выезжают снизу по очереди. Задержка у подписи маленькая -
   при большей она читается как отдельное событие, а это одна фраза. */
.ad-tanim-rise .ad-banner-title { animation: ad-rise calc(var(--ad-t, 1s) * .7) cubic-bezier(.2, .8, .25, 1) both; }
.ad-tanim-rise .ad-banner-sub   { animation: ad-rise calc(var(--ad-t, 1s) * .7) cubic-bezier(.2, .8, .25, 1) calc(var(--ad-t, 1s) * .18) both; }
@keyframes ad-rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }

/* Выезд сбоку. Текст прижат влево, поэтому и выезжает слева - справа он шёл бы навстречу
   собственному выравниванию. */
.ad-tanim-slide .ad-banner-title { animation: ad-slide calc(var(--ad-t, 1s) * .8) cubic-bezier(.2, .8, .25, 1) both; }
.ad-tanim-slide .ad-banner-sub   { animation: ad-slide calc(var(--ad-t, 1s) * .8) cubic-bezier(.2, .8, .25, 1) calc(var(--ad-t, 1s) * .16) both; }
@keyframes ad-slide { from { opacity: 0; transform: translateX(-28px); } to { opacity: 1; transform: none; } }

/* Разбег: буквы стоят сбитыми и расходятся по местам.

   Раньше движение шло НАОБОРОТ - от разрядки .42em к нормальной, - и в этом был весь косяк.
   На заголовке из шестнадцати знаков .42em это лишних полторы сотни пикселей: строка не
   влезала в баннер, переносилась на вторую, и в первый миг вся раскладка прыгала. Ширину
   строки в стилях не измерить, значит любое начальное значение рано или поздно кого-то
   сломает - поэтому разгон идёт в СЖАТУЮ сторону. Уже строки, чем есть, она не станет
   никогда, и сломать нечего.

   Конечное значение берём из настройки разрядки, а не ноль - иначе движение заканчивалось
   бы не там, где человек её выставил. */
.ad-tanim-spread .ad-banner-title { animation: ad-spread calc(var(--ad-t, 1s) * 1.2) cubic-bezier(.2, .8, .25, 1) both; }
@keyframes ad-spread {
  from { opacity: 0; letter-spacing: -.1em; }
  to   { opacity: 1; letter-spacing: var(--ad-track, 0em); }
}
.ad-tanim-spread .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .9) ease-out calc(var(--ad-t, 1s) * .45) both; }

/* Из тумана: текст наводится на резкость. Размытие снимается вместе с тенью - она часть
   букв, и оставить её чёткой значило бы показать тень раньше самих букв. */
.ad-tanim-sharpen .ad-banner-title,
.ad-tanim-sharpen .ad-banner-sub { animation: ad-sharpen calc(var(--ad-t, 1s) * 1.1) ease-out both; }
@keyframes ad-sharpen { from { opacity: 0; filter: blur(9px); } to { opacity: 1; filter: blur(0); } }

/* По буквам. Заголовок разбирается на отдельные буквы скриптом, номер каждой лежит в --i -
   из него и считается задержка. Пробелы сохраняем через white-space: pre, иначе слова
   слиплись бы: буква в inline-block пробел рядом с собой не удерживает. */
.ad-tanim-letters .ad-banner-title span,
.ad-tanim-flip .ad-banner-title span,
.ad-tanim-wave .ad-banner-title span {
  display: inline-block;
  white-space: pre;
}

.ad-tanim-letters .ad-banner-title span {
  animation: ad-letter-in calc(var(--ad-t, 1s) * .5) cubic-bezier(.2, .8, .25, 1) both;
  animation-delay: calc(var(--i, 0) * var(--ad-t, 1s) * .038);
}
@keyframes ad-letter-in { from { opacity: 0; transform: translateY(14px) scale(.94); } to { opacity: 1; transform: none; } }
.ad-tanim-letters .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .8) ease-out calc(var(--ad-t, 1s) * .5) both; }

/* Переворот: каждая буква поворачивается вокруг горизонтальной оси. Перспектива задана на
   заголовке - без неё rotateX даёт плоское сжатие по высоте, а не поворот. */
.ad-tanim-flip .ad-banner-title { perspective: 420px; }
.ad-tanim-flip .ad-banner-title span {
  transform-origin: 50% 65%;
  animation: ad-letter-flip calc(var(--ad-t, 1s) * .6) cubic-bezier(.2, .8, .3, 1) both;
  animation-delay: calc(var(--i, 0) * var(--ad-t, 1s) * .05);
}
@keyframes ad-letter-flip { from { opacity: 0; transform: rotateX(-92deg); } to { opacity: 1; transform: none; } }
.ad-tanim-flip .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .8) ease-out calc(var(--ad-t, 1s) * .5) both; }

/* Волна - единственное движение букв, которое идёт постоянно. Размах крошечный: баннер
   стоит первым экраном, и всё, что заметно прыгает, через минуту начинает раздражать. */
.ad-tanim-wave .ad-banner-title span {
  animation: ad-letter-wave calc(var(--ad-t, 1s) * 2.4) ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * var(--ad-t, 1s) * .06);
}
@keyframes ad-letter-wave { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3.5px); } }

/* Пульс. Точка роста - левый край: заголовок прижат влево, и от центра он уползал бы за
   границу баннера. */
.ad-tanim-pulse .ad-banner-title {
  transform-origin: left center;
  animation: ad-pulse calc(var(--ad-t, 1s) * 3.2) ease-in-out infinite;
}
@keyframes ad-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.045); } }

/* Свечение: подсветка самих букв. Добавляется К выбранной тени, а не заменяет её - иначе
   включённое свечение стирало бы и сторону тени, и объём букв, две настройки, которые
   человек только что выставил. */
.ad-tanim-glow .ad-banner-title { animation: ad-glow calc(var(--ad-t, 1s) * 3.6) ease-in-out infinite; }
@keyframes ad-glow {
  0%, 100% { text-shadow: var(--ad-shadow, 0 2px 10px rgba(0, 0, 0, .55)); }
  50%      { text-shadow: 0 0 20px rgba(255, 255, 255, .8), var(--ad-shadow, 0 2px 10px rgba(0, 0, 0, .55)); }
}

/* ── Движение фона ─────────────────────────────────────────────────────────────────────── */

/* Было 24 секунды на 12% - полпроцента в секунду, увидеть это нельзя. Размах 26%, а
   длительность зависит от ползунка скорости. */
.ad-banim-zoom .ad-banner-bg { animation: ad-slow-zoom calc(var(--ad-t, 1s) * 12) ease-in-out infinite alternate; }
@keyframes ad-slow-zoom {
  from { transform: scale(var(--ad-bg-scale, 1)); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.26)); }
}

/* Панорама: снимок медленно едет вбок. Запас по масштабу нужен обязательно - без него
   сдвиг обнажил бы пустой край. */
.ad-banim-pan .ad-banner-bg { animation: ad-pan calc(var(--ad-t, 1s) * 13) ease-in-out infinite alternate; }
@keyframes ad-pan {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22)) translateX(-8%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22)) translateX(8%); }
}

/* Проплыв: наплыв и панорама разом, по диагонали. Самое спокойное из движений фона -
   ни одно направление не выражено, и глаз ни за что не цепляется. */
.ad-banim-drift .ad-banner-bg { animation: ad-drift calc(var(--ad-t, 1s) * 18) ease-in-out infinite alternate; }
@keyframes ad-drift {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translate(-4.5%, -2.5%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.30)) translate(4.5%, 2.5%); }
}

/* Качание. Поворот меньше полутора градусов: больше - и баннер выглядит криво повешенным,
   а не живым. Запас по масштабу закрывает углы, которые поворот выводит за края. */
.ad-banim-sway .ad-banner-bg { animation: ad-sway calc(var(--ad-t, 1s) * 10) ease-in-out infinite alternate; }
@keyframes ad-sway {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) rotate(-1.2deg); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) rotate(1.2deg); }
}

/* Дыхание: свет над снимком то ярче, то мягче. Размытие в обеих точках повторяем явно -
   filter свойство целиком, и упоминание одной яркости стёрло бы выбранное размытие. */
.ad-banim-breathe .ad-banner-bg { animation: ad-breathe calc(var(--ad-t, 1s) * 4.5) ease-in-out infinite; }
@keyframes ad-breathe {
  0%, 100% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1); }
  50%      { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1.16); }
}

/* Блик и Засветка живут на ::before, а НЕ на ::after. У ::after баннера уже есть работа -
   цветная полоса слева, - и специфичность у правил одинаковая, значит побеждало то, что
   ниже в файле. Блик стоял ниже и молча стирал полосу: включаешь движение, а с баннера
   пропадает деталь, к движению отношения не имеющая. */
.ad-banim-shine::before {
  content: "";
  position: absolute; top: 0; bottom: 0; width: 40%;
  z-index: 1;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .26), transparent);
  transform: skewX(-16deg);
  animation: ad-shine-move calc(var(--ad-t, 1s) * 6) ease-in-out infinite;
  pointer-events: none;
}
@keyframes ad-shine-move { 0% { left: -50%; } 55% { left: 120%; } 100% { left: 120%; } }

/* Засветка: мягкое пятно света проходит наискось. В отличие от блика у него нет кромки -
   это не полоса, а освещение, поэтому на пёстрой фотографии оно не режет глаз. */
.ad-banim-flare::before {
  content: "";
  position: absolute;
  top: -40%; bottom: -40%; left: -30%;
  width: 60%;
  z-index: 1;
  background: radial-gradient(ellipse at center, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0) 62%);
  animation: ad-flare-move calc(var(--ad-t, 1s) * 9) ease-in-out infinite;
  pointer-events: none;
}
@keyframes ad-flare-move {
  0%   { transform: translateX(-40%); opacity: 0; }
  25%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { transform: translateX(280%); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ad-tanim-fade .ad-banner-body,
  .ad-tanim-rise .ad-banner-title,
  .ad-tanim-rise .ad-banner-sub,
  .ad-tanim-slide .ad-banner-title,
  .ad-tanim-slide .ad-banner-sub,
  .ad-tanim-spread .ad-banner-title,
  .ad-tanim-spread .ad-banner-sub,
  .ad-tanim-sharpen .ad-banner-title,
  .ad-tanim-sharpen .ad-banner-sub,
  .ad-tanim-letters .ad-banner-title span,
  .ad-tanim-letters .ad-banner-sub,
  .ad-tanim-flip .ad-banner-title span,
  .ad-tanim-flip .ad-banner-sub,
  .ad-tanim-wave .ad-banner-title span,
  .ad-tanim-pulse .ad-banner-title,
  .ad-tanim-glow .ad-banner-title,
  .ad-banim-zoom .ad-banner-bg,
  .ad-banim-pan .ad-banner-bg,
  .ad-banim-drift .ad-banner-bg,
  .ad-banim-sway .ad-banner-bg,
  .ad-banim-breathe .ad-banner-bg,
  .ad-banim-shine::before,
  .ad-banim-flare::before { animation: none; }
}

/* Выбор оформления: ряды кнопок-переключателей. Компактные, чтобы три ряда не растянули
   окно правки на два экрана. */
.style-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 6px 0 14px;
}

.style-chip {
  padding: 8px 12px;
  border: 1.5px solid var(--border);
  border-radius: 12px 3px 12px 3px;
  background: none;
  color: var(--fg-dim);
  font: 600 12.5px/1 inherit;
  font-family: inherit;
  cursor: pointer;
  transition: border-color .2s, color .2s, background .2s;
}

.style-chip.on {
  border-color: var(--pine);
  color: var(--pine);
  background: var(--pine-soft);
}

/* Живой пример внутри рамки выбора. Это НАСТОЯЩИЙ баннер теми же классами - снимаем только
   то, что относится к его месту на главной: внешние отступы, тень и собственную форму
   (форму задаёт сама рамка). Так пример не может разойтись с оригиналом. */
.ad-preview-banner {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border-radius: inherit;
  box-shadow: none;
  aspect-ratio: auto;
  pointer-events: none;
}

/* Пример мельче настоящего баннера, поэтому текст пропорционально уменьшен - иначе
   заголовок в рамке выглядел бы крупнее, чем окажется на экране. */
/* Пример уже настоящего баннера, поэтому текст уменьшен ровно во столько же раз - иначе
   заголовок в рамке выглядел бы крупнее, чем окажется на экране. Коэффициент один на всё:
   отдельных размеров под каждый шрифт больше нет, размер задаёт сам человек. */
.ad-preview-banner .ad-banner-title { font-size: calc(var(--ad-title-size, 26px) * .77); }
.ad-preview-banner .ad-banner-sub   { font-size: calc(var(--ad-title-size, 26px) * .4); }
.ad-preview-banner .ad-banner-body { padding: 14px 14px 14px 16px; }

/* ── Ползунки оформления: размытие и затемнение ──────────────────────────────────────────
   Вместо ряда кнопок - линия. У этих двух настроек нет «правильных» ступеней: сколько
   размытия нужно, зависит от самого снимка, и три заготовленных варианта регулярно
   промахивались. Линия даёт любое значение и сразу показывает результат на живом примере.

   Значение печатаем рядом с названием: без него человек тянет ползунок вслепую и не может
   вернуться к тому, что уже подобрал. */
.slider-field {
  display: flex;
  flex-direction: column;
  margin: 0 0 14px;
}

.slider-field .field-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.slider-val {
  font-weight: 700;
  font-size: 12.5px;
  color: var(--pine);
  font-variant-numeric: tabular-nums;   /* иначе число дёргается при перетягивании */
}

/* appearance: none обязателен на всех трёх - иначе браузер рисует свой ползунок поверх
   нашего и на iOS он остаётся синим системным. */
.style-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 26px;              /* палец попадает по всей высоте, а не по нитке в 4px */
  margin: 6px 0 0;
  background: none;
  cursor: pointer;
  touch-action: none;        /* иначе тянущий жест перехватывает прокрутка окна правки */
}

.style-slider::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: var(--border);
}
.style-slider::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: var(--border);
}

.style-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  margin-top: -8px;          /* центрирует кружок на дорожке: webkit считает от её верха */
  border-radius: 50%;
  background: var(--pine);
  border: 3px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .22);
}
.style-slider::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--pine);
  border: 3px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .22);
}

/* ── Ряд шрифтов: каждая кнопка написана своим шрифтом ───────────────────────────────────
   Список названий бесполезен, пока не видно начертания: «Didot» и «Baskerville» человеку
   ничего не говорят, а разницу между ними он видит сразу. */
.font-row .style-chip { font-size: 13px; }
.f-helvetica   { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
.f-avenir      { font-family: "Avenir Next", Avenir, "Segoe UI", Roboto, sans-serif; }
.f-futura      { font-family: Futura, "Trebuchet MS", "Century Gothic", sans-serif; }
.f-optima      { font-family: Optima, Candara, "Gill Sans", sans-serif; }
.f-verdana     { font-family: Verdana, Tahoma, sans-serif; }
.f-trebuchet   { font-family: "Trebuchet MS", Tahoma, sans-serif; }
.f-georgia     { font-family: Georgia, "Times New Roman", serif; }
.f-times       { font-family: "Times New Roman", Times, serif; }
.f-didot       { font-family: Didot, "Bodoni MT", Georgia, serif; }
.f-baskerville { font-family: Baskerville, "Palatino Linotype", Palatino, serif; }
.f-palatino    { font-family: Palatino, "Palatino Linotype", "Book Antiqua", serif; }
.f-copperplate { font-family: Copperplate, "Copperplate Gothic Light", Optima, sans-serif; }
.f-typewriter  { font-family: "American Typewriter", "Courier New", Courier, monospace; }
.f-menlo       { font-family: Menlo, Consolas, "Courier New", monospace; }
.f-marker      { font-family: "Marker Felt", "Comic Sans MS", cursive; }
.f-snell       { font-family: "Snell Roundhand", "Brush Script MT", cursive; font-size: 15px; }

/* ── Квадрат направления тени ────────────────────────────────────────────────────────────
   Девять клеток вместо строки названий: направление - это место, и квадрат показывает его
   прямо. «Снизу-справа» в списке пришлось бы каждый раз мысленно переводить в картинку. */
.dir-pad {
  display: grid;
  grid-template-columns: repeat(3, 44px);
  gap: 4px;
  margin: 6px 0 14px;
}

.dir-pad button {
  height: 40px;
  border: 1.5px solid var(--border);
  border-radius: 10px 3px 10px 3px;
  background: none;
  color: var(--fg-dim);
  font: 600 15px/1 inherit;
  font-family: inherit;
  cursor: pointer;
  transition: border-color .2s, color .2s, background .2s;
}

.dir-pad button.dir-none { font-size: 11.5px; }

.dir-pad button.on {
  border-color: var(--pine);
  color: var(--pine);
  background: var(--pine-soft);
}

/* ── Живой пример прилипает к верху окна правки ──────────────────────────────────────────
   Настроек стало полтора десятка, на экран они не помещаются. При прокрутке вниз баннер
   уезжал, и человек тянул ползунок тени, не видя, что тень делает - настраивал вслепую и
   проверял, только закрыв окно. Теперь пример остаётся на месте, а под ним проезжает всё
   остальное.

   Отрицательные поля по бокам гасят внутренний отступ карточки: липкая шапка обязана
   закрывать содержимое ОТ КРАЯ ДО КРАЯ, иначе строки видны в двадцатипиксельных щелях
   слева и справа и это читается как поломка. */
.ad-preview-sticky {
  position: sticky;
  top: -14px;                /* заход под верх ровно на свой отступ - без щели над рамкой */
  z-index: 6;
  background: #fff;
  margin: 0 -16px 12px;
  padding: 14px 16px 8px;
  /* Тень только снизу и только на просвет: она показывает, что под шапкой что-то есть,
     и не рисует лишней рамки, когда прокрутка в самом верху. */
  box-shadow: 0 8px 12px -10px rgba(20, 24, 28, .5);
}

/* Кнопка под примером - компактная строкой, чтобы липкая шапка не съедала пол-экрана. */
.ad-preview-sticky .ad-photo-actions { margin-top: 8px; }
/* Объём убран (Кирилл 2026-08-27). 3D требовал стопки слоёв тени, градиента на самой букве
   и проверки, умеет ли браузер обрезать заливку по контуру текста, - много механики ради
   вида, который на баннере 3.2:1 и так почти не считывался. Контур делает ту же работу
   проще: отделяет букву от снимка, и его толщину можно вести плавно ползунком. Толщина
   приходит переменной --ad-stroke, правил под неё не нужно. */

/* ── Баннер без фото ─────────────────────────────────────────────────────────────────────
   Картинка в объявлении необязательна, и пост без неё выходит на тёмной подложке. Пример в
   форме заказа обязан показывать ровно это: раньше он просто прятался, а вместе с ним
   прятались все настройки - человек открывал форму и не видел ни шрифтов, ни теней.

   Цвет тот же, что у купленного баннера на первом экране (.ad-banner-sold), чтобы пример и
   результат совпадали, а не «примерно совпадали». */
.ad-banner.no-photo,
.ad-preview-banner.no-photo {
  background: linear-gradient(120deg, #16324f, #1f4d72);
  animation: none;
}

/* Свечение места продажи на своём же посте неуместно: место уже куплено. */
.ad-banner.no-photo .ad-banner-glow { display: none; }

/* Подсказка под рамкой, а не поверх примера. */
.ad-photo-hint { margin-top: 8px; text-align: center; }

/* ── Настройки, которым нужен снимок ─────────────────────────────────────────────────────
   Размытие фото и движение фона без картинки не могут сделать ничего. Раньше они прятались -
   и набор настроек выглядел разным до и после загрузки: высота панели прыгала, а элементы
   появлялись из ниоткуда. Теперь они на месте, но погашены: видно, что они есть и чего ждут.

   Прозрачность идёт вместе с pointer-events: ползунок, который двигается, но ни на что не
   влияет, врёт хуже, чем погашенный. Переход плавный - как только фото появляется, настройки
   оживают на глазах, а не возникают рывком. */
.style-locked {
  opacity: .3;
  pointer-events: none;
  filter: grayscale(1);
  transition: opacity .35s ease, filter .35s ease;
}

/* Обратный переход задан на самих полях, а не только на погашенном состоянии: иначе
   оживление было бы мгновенным, а гашение плавным - несимметрично и оттого заметно. */
#adEditBlurField,
#adEditBanimField {
  transition: opacity .35s ease, filter .35s ease;
}

/* Два действия над снимком в одну строку: поправить кадр (карандаш на самом снимке) и
   заменить его целиком. Раньше их пыталась делать одна кнопка, и поправить кадр было
   нечем - чтобы сдвинуть картинку, приходилось искать файл в галерее заново. */
.ad-photo-actions {
  display: flex;
  gap: 8px;
  margin-top: 8px;
  flex-wrap: wrap;
}

.ad-photo-actions .idea-btn { margin-top: 0; }

/* Действия над поставщиком одной строкой: каждая кнопка во всю ширину съедала экран, а их
   до трёх. Удаление слева, одобрение справа - палец на телефоне чаще идёт вправо, и там
   должно стоять безобидное действие, а не то, которое стирает данные. */
.admin-sup-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

.admin-sup-actions button {
  flex: 1 1 0;
  min-width: 0;
  margin: 0;
  white-space: nowrap;
  padding-left: 6px;
  padding-right: 6px;
}

/* Одобрение - единственное здесь действие, которое ничего не ломает, поэтому оно и
   выглядит как обычная кнопка, а не как предупреждение. */
.admin-sup-actions .admin-approve-btn { color: var(--pine); font-weight: 600; }

/* ── Разбор записей о сбоях ──────────────────────────────────────────────────────────────
   Полсотни записей подряд читались как полсотни проблем. На деле сканеры, прямые заходы и
   медленная сеть выглядели так же тревожно, как ошибка в коде, - и понять, что именно чинить,
   было нельзя. Теперь приговор стоит ПЕРЕД подробностями, а сверху сводка. */
.boot-log-summary {
  padding: 10px 14px;
  margin-bottom: 10px;
  border-radius: 12px 3px 12px 3px;
  background: #FCEBEA;
  color: #8A2F28;
  font-size: 13.5px;
  font-weight: 600;
}

/* Когда чинить нечего - и цвет спокойный. Тревожный фон над строкой «ничего не нужно»
   противоречил бы собственному тексту. */
.boot-log-summary-ok { background: var(--pine-soft); color: var(--pine); }

.boot-log-verdict {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fg-dim);
  margin-bottom: 6px;
  line-height: 1.35;
}

.boot-log-verdict-ours { color: #A3352C; }

/* ── Ссылки и статистика ─────────────────────────────────────────────────────
   Форма создания и карточки статистики. Графики рисуются инлайновым SVG:
   внешних библиотек в проекте нет и заводить их ради двух столбиков нельзя -
   любой CDN это ещё один домен, который у российского провайдера может не
   открыться. */
.link-form { display: flex; flex-direction: column; gap: 6px; }
.link-label { font-size: 14px; font-weight: 600; color: #1f2937; margin-top: 12px; }
.link-req { color: #dc2626; }
.link-input {
  width: 100%; box-sizing: border-box; padding: 12px 14px;
  border: 1px solid #e5e7eb; border-radius: 12px; font-size: 16px;
  background: #fff; color: #111827; outline: none;
}
.link-input:focus { border-color: #0f9d58; }
.link-hint { font-size: 13px; color: #6b7280; line-height: 1.4; margin-top: 4px; }

.link-modes { display: flex; gap: 8px; margin-top: 4px; }
.link-mode {
  flex: 1; padding: 12px 8px; border-radius: 12px; border: 1px solid #e5e7eb;
  background: #fff; color: #374151; font-size: 15px; font-weight: 600; cursor: pointer;
}
.link-mode.is-on { border-color: #0f9d58; background: #ecfdf5; color: #065f46; }

/* Карточка ссылки в списке */
.link-card { background: #fff; border-radius: 16px; padding: 14px 16px; margin-bottom: 10px; }
.link-card-title { font-size: 16px; font-weight: 700; color: #111827; }
.link-card-platform { font-size: 13px; color: #6b7280; margin-top: 2px; }
.link-url {
  font-size: 13px; color: #0f9d58; word-break: break-all; margin-top: 8px;
  background: #f9fafb; padding: 8px 10px; border-radius: 10px;
}
.link-funnel { display: flex; gap: 6px; margin-top: 10px; }
.link-step { flex: 1; text-align: center; background: #f9fafb; border-radius: 10px; padding: 8px 4px; }
.link-step-n { font-size: 18px; font-weight: 700; color: #111827; }
.link-step-l { font-size: 11px; color: #6b7280; margin-top: 2px; }
.link-actions { display: flex; gap: 8px; margin-top: 10px; }
.link-btn {
  flex: 1; padding: 10px; border-radius: 10px; border: 1px solid #e5e7eb;
  background: #fff; font-size: 14px; font-weight: 600; color: #374151; cursor: pointer;
}
.link-btn-main { background: #0f9d58; border-color: #0f9d58; color: #fff; }

/* Блоки статистики */
.stat-box { background: #fff; border-radius: 16px; padding: 14px 16px; margin-bottom: 10px; }
.stat-box-h { font-size: 14px; font-weight: 700; color: #111827; margin-bottom: 10px; }
.stat-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; font-size: 14px; }
.stat-row-name { color: #374151; }
.stat-row-val { font-weight: 700; color: #111827; }
.stat-money { font-size: 22px; font-weight: 700; color: #0f9d58; }
.stat-money-l { font-size: 12px; color: #6b7280; }
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.stat-cell { background: #f9fafb; border-radius: 12px; padding: 10px 12px; }

/* Горизонтальная полоска для разбивок */
.bar-wrap { margin: 6px 0; }
.bar-top { display: flex; justify-content: space-between; font-size: 13px; color: #374151; }
.bar-track { height: 6px; background: #f3f4f6; border-radius: 3px; margin-top: 4px; overflow: hidden; }
.bar-fill { height: 100%; background: #0f9d58; border-radius: 3px; }

/* Пояснение под выбором режима. Не мелкий серый хвостик, а заметный блок:
   от этого выбора зависит, увидит человек цену клиента или нет, и узнать об
   этом ПОСЛЕ запуска рекламы - значит потратить бюджет вслепую. */
.link-note {
  border-radius: 12px; padding: 12px 14px; margin-top: 10px;
  font-size: 13px; line-height: 1.5;
}
.link-note-ok { background: #ecfdf5; color: #065f46; }
.link-note-warn { background: #fffbeb; color: #92400e; }
.link-note b { font-weight: 700; }

/* ══════════════════════════════════════════════════════════════════════════
   Чаты рынков. Три вкладки сверху, лента, поле ввода снизу.
   Фото, видео и голосовых нет намеренно - решение Кирилла. Аватары и переход
   в профиль тоже: из чата нельзя провалиться в человека, бэкенд их и не отдаёт.
   ══════════════════════════════════════════════════════════════════════════ */

.home-chats-btn {
  width: 100%; margin: 4px 0 10px; padding: 15px 18px;
  border-radius: 18px 4px 18px 4px; border: 1px solid var(--border);
  background: var(--bg); color: var(--fg);
  font-family: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  box-shadow: 0 2px 6px rgba(20, 24, 28, .06), 0 8px 20px rgba(20, 24, 28, .07);
}
.home-chats-badge {
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px;
  background: #dc2626; color: #fff; font-size: 12px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}

/* Чат - не обычный экран, а панель на всё свободное место под шапкой приложения.
   Причина: переключение рынков и кнопка отправки должны быть под рукой ВСЕГДА, а не
   уезжать вместе с историей. Поэтому прокручивается только лента; вкладки прибиты
   сверху, поле ввода - снизу. Верхнюю границу считает скрипт по фактическому низу
   шапки: шапка липкая, содержит рекламный баннер (то есть, то нет) и целиком прячется
   при открытой клавиатуре - заранее написанная формула ошибалась бы каждый раз. */
.chat-view {
  position: fixed; left: 0; right: 0; bottom: 0;
  max-width: 560px; margin: 0 auto;
  padding: 0 16px calc(8px + var(--safe-b));
  display: flex; flex-direction: column;
  z-index: 4;                 /* под шапкой (5), над остальным */
  background: var(--bg-page, #f5f5f7);
  box-sizing: border-box;
}
.chat-view.hidden { display: none; }

/* Шапка чата прибита: прокручивается только лента (chat-feed), а строка
   рынков остаётся на месте при любой длине переписки. */
.chat-head { flex: 0 0 auto; padding-top: 6px; }

/* Одна строка: «Назад», рынки, звук. Вертикаль на телефоне дороже горизонтали -
   каждая сэкономленная строка это ещё одно видимое сообщение.

   Всё в строке ровно 30 пикселей высотой и центрировано по одной линии. Раньше
   высота вкладки набиралась из полей и строки текста и выходила больше круглых
   кнопок - строка стояла ступенькой. Теперь высота задана числом всем троим,
   а знаки внутри круглых кнопок центрируются flex-ом, а не подбором полей:
   стрелка и колокольчик разной ширины, и на полях они вставали по-разному. */
.chat-bar { display: flex; align-items: center; gap: 6px; min-width: 0; }

.chat-back {
  flex: 0 0 auto; box-sizing: border-box; width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  font-size: 15px; line-height: 1; cursor: pointer; font-family: inherit;
  padding: 0; display: flex; align-items: center; justify-content: center;
}

/* Кнопка звука. Оповещения о новых сообщениях легко надоедают, а искать выключатель
   в настройках человек не станет - он просто уйдёт из чата. Поэтому переключатель
   стоит там же, где сам чат. */
.chat-sound {
  flex: 0 0 auto; box-sizing: border-box; width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg);
  font-size: 13px; line-height: 1; cursor: pointer; font-family: inherit;
  padding: 0; display: flex; align-items: center; justify-content: center;
}
.chat-sound.is-off { opacity: .45; }

/* Черта стоит НАД строкой кнопок: отступ сверху ей больше не нужен - её
   и так отделяет поле шапки, - а снизу нужен, чтобы кнопки не лежали на ней. */
.chat-divider { height: 1px; background: var(--border); margin: 0 0 8px; }

/* Строку с именем рынка убрали: во вкладке оно и так подсвечено целиком,
   а повтор съедал высоту - на телефоне это минус одно видимое сообщение. */
.chat-foot { flex: 0 0 auto; padding-top: 6px; }

.chat-tabs { display: flex; gap: 6px; flex: 1 1 auto; min-width: 0; }
/* Шрифт мелкий намеренно: названия рынков должны помещаться ЦЕЛИКОМ, без сокращений.
   «Ю. Ворота» человек читает на полсекунды дольше, чем «Южные Ворота», а полсекунды
   на каждом переключении складываются в раздражение.

   Высота задана числом и равна высоте круглых кнопок по краям. Раньше она набиралась
   из полей и строки текста и выходила на четыре пикселя больше: три вкладки стояли
   выше «Назад» и звонка, и вся строка выглядела ступенькой.

   box-sizing здесь обязателен: рамка в один пиксель с каждой стороны иначе прибавляется
   К заданной высоте, и выравнивание, ради которого всё и делалось, снова уезжает.

   Многоточие - страховка на самый узкий экран, а не режим работы. На 360 и шире
   имена помещаются целиком; на 320 текст раньше вылезал за свою рамку и наезжал
   на соседнюю вкладку - это выглядело поломкой, а обрезанное слово выглядит
   обрезанным словом. */
.chat-tab {
  flex: 1 1 0; min-width: 0; box-sizing: border-box;
  height: 30px; padding: 0 4px; border-radius: 11px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-dim);
  font-family: inherit; font-size: 11px; font-weight: 600; cursor: pointer;
  position: relative; white-space: nowrap; letter-spacing: -0.2px;
  /* Блок со строчной высотой, а НЕ flex-центрирование. В прошлый раз я поставил
     сюда flex вместе с многоточием - и многоточие не работало вовсе: во flex-
     контейнере голый текст становится безымянным элементом сетки, а text-overflow
     умеет резать только строчный поток. Рамка оставалась на месте, а слово
     по-прежнему вылезало за неё и наезжало на соседнюю вкладку.
     line-height 28 = высота 30 минус две рамки по пикселю: текст встаёт по центру
     сам, без второго механизма выравнивания. */
     line-height 28 = высота 30 минус две рамки по пикселю: текст встаёт по центру
     сам, без второго механизма выравнивания.
     Счётчик онлайна стоит В ТОЙ ЖЕ строке, что название («Садовод (2)»), -
     поэтому высота осталась прежней, а многоточие продолжает работать: оно
     живёт на .chat-tab-name, который остался строчным потоком. */
  display: block; text-align: center; line-height: 28px;
  overflow: hidden; text-overflow: ellipsis;
}

/* Название вместе со счётчиком. Обрезка многоточием живёт здесь, а не на
   кнопке: во flex-контейнере text-overflow не работает, это уже ловили. */
.chat-tab-name {
  display: block; line-height: 28px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Число в скобках. Чуть мельче названия и обычным начертанием: это справка,
   а не часть имени рынка, и утяжелять её незачем. */
.chat-tab-online { font-size: 10px; font-weight: 600; opacity: .65; }
/* Онлайн - второй строкой под названием («Садовод» / «онлайн 3»): число в скобках
   рядом с именем не читалось как онлайн. Высота вкладки прежняя, 30px: две
   строки 13+11 помещаются в 28 внутри рамки. Многоточие у имени сохраняется. */
.chat-tab { line-height: normal; padding-top: 2px; }
.chat-tab .chat-tab-name { line-height: 13px; }
.chat-tab .chat-tab-online {
  display: block; font-size: 9px; line-height: 11px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-tab-online.is-live { opacity: 1; color: var(--pine, #0f9d58); }
.chat-tab.is-on .chat-tab-online.is-live { color: #047857; }
.chat-tab.is-on { border-color: var(--pine, #0f9d58); background: #ecfdf5; color: #065f46; }
.chat-tab-dot {
  position: absolute; top: 4px; right: 4px;
  width: 7px; height: 7px; border-radius: 50%; background: #dc2626;
}

/* Лента - единственное, что прокручивается. Высоту не задаём: её распределяет
   flex, забирая всё, что осталось между прибитыми вкладками и полем ввода. */
.chat-feed {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  display: flex; flex-direction: column; gap: 8px;
  padding: 4px 0 8px;
  overscroll-behavior: contain;
}
/* Лента растёт СНИЗУ, как в Телеграме: пока сообщений мало, они прижаты
   к полю ввода, а не висят под шапкой посреди пустоты. Отступ на первом
   элементе, а не justify-content: flex-end - последний ломает прокрутку,
   когда сообщений становится больше экрана. */
.chat-feed > :first-child { margin-top: auto; }

.chat-day { text-align: center; font-size: 12px; color: var(--fg-dim); margin: 10px 0 2px; }

.chat-msg { max-width: 82%; align-self: flex-start; }
.chat-msg.is-mine { align-self: flex-end; }
/* Новое сообщение въезжает снизу и проявляется. Анимируем ТОЛЬКО свежие:
   полсотни пузырей истории, выезжающих разом, - это не появление, а рябь. */
.chat-msg.is-new { animation: chatIn .28s cubic-bezier(.2, .9, .3, 1.2) both; }
@keyframes chatIn {
  from { opacity: 0; transform: translateY(14px) scale(.97); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .chat-msg.is-new { animation: none; }
}

.chat-author {
  font-size: 12px; font-weight: 700; color: var(--pine, #0f9d58);
  margin: 0 0 3px 12px;
}
.chat-author-badge { font-size: 11px; }

.chat-bubble {
  padding: 9px 13px; border-radius: 16px 16px 16px 4px;
  background: var(--bg); color: var(--fg);
  font-size: 15px; line-height: 1.4; word-break: break-word; white-space: pre-wrap;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .06);
}
.chat-msg.is-mine .chat-bubble {
  border-radius: 16px 16px 4px 16px;
  background: #ecfdf5; color: #06301f;
}
.chat-bubble.is-deleted { color: var(--fg-dim); font-style: italic; }

.chat-time { font-size: 11px; color: var(--fg-dim); margin: 3px 12px 0; }
.chat-msg.is-mine .chat-time { text-align: right; }

.chat-report {
  font-size: 11px; color: var(--fg-dim); margin: 2px 12px 0;
  background: none; border: none; padding: 0; cursor: pointer; font-family: inherit;
}

/* Пометка правки - рядом с текстом, мельче времени: это справка, а не часть сообщения. */
.chat-edited {
  font-size: 11px; color: var(--fg-dim); margin-left: 6px; font-style: italic;
}

/* Пузырь и кнопки администратора - в одной строке, а не друг на друге. Для своих
   сообщений (is-mine, пузырь справа) кнопки идут ПОСЛЕ пузыря - то есть правее
   него, у края экрана. Для чужих (пузырь слева) - тоже после, то есть сразу
   правее пузыря, но уже не у края. В обоих случаях порядок в разметке один и
   тот же (bub, затем adminBox) - разворот делает сам flex, а не JS. */
.chat-bubble-row {
  display: flex; align-items: flex-end; gap: 6px; max-width: 100%;
}
.chat-msg.is-mine .chat-bubble-row { justify-content: flex-end; }
.chat-bubble-row .chat-bubble { max-width: 100%; }
/* Две кнопки по 34px занимают ~80px строки. На узком экране пузырь в обычных
   82% сжался бы в столбик из двух слов - у админских сообщений даём запас. */
.chat-msg.has-admin { max-width: 97%; }

/* Столбик из двух кнопок сбоку от пузыря - никогда не поверх текста, поэтому
   и не может наехать ни на слово, ни друг на друга. */
.chat-admin-box {
  flex: 0 0 auto;
  display: flex; flex-direction: row; gap: 6px; align-items: center;
}
/* 34px - минимум, в который уверенно попадает палец. 22px были размером под
   курсор мыши, а чат открывают с телефона. */
/* 34px - минимум, в который уверенно попадает палец. 22px были размером под
   курсор мыши, а чат открывают с телефона. */
.chat-admin-edit, .chat-admin-delete {
  width: 34px; height: 34px; border-radius: 50%;
  border: none; background: var(--card); color: var(--fg-dim);
  font-size: 15px; line-height: 34px; text-align: center; padding: 0;
  cursor: pointer; font-family: inherit;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .12);
  /* Переход, а не keyframes: анимация по ключевым кадрам играет один раз при
     появлении класса, а роль кнопки меняется туда-обратно сколько угодно.
     Переход отрабатывает КАЖДУЮ смену, в обе стороны - и туда, и обратно. */
  transition: background-color .22s ease, color .22s ease;
}
.chat-admin-delete:active, .chat-admin-edit:active { background: var(--border); }

/* Значок внутри кнопки: подменяется карандаш <-> галочка. Разворотов нет -
   карандаш в своём обычном виде. */
.chat-admin-ico { display: inline-block; line-height: 1; }
/* Карандаш смотрит в другую сторону. Отражаем ЗНАЧОК, а не кнопку: на кнопке
   это зеркалило бы и галочку, в которую он превращается при правке. */
.chat-admin-ico.is-flip { transform: scaleX(-1); }

/* Режим правки: те же две кнопки меняют смысл. Галочка подтверждает, крестик
   отменяет. Плавный поворот - чтобы смена роли не прошла незамеченной: иначе
   человек жмёт крестик, ожидая удаления, и получает отмену правки.
   ВАЖНО: никакого scaleX. Отражение по горизонтали ломало сам знак галочки -
   она рисовалась зеркальной и читалась как посторонний символ. Поворот
   сохраняет форму знака, отражение - нет. */
/* Меняется только цвет и сам знак. Поворот кнопки убран: карандаш,
   крутящийся вокруг оси, отвлекал от того, что он стал галочкой. */
.chat-admin-edit.is-confirm {
  background: var(--accent, #0f9d58); color: #fff;
}
.chat-admin-delete.is-cancel {
  background: var(--card); color: var(--fg);
}

/* Поле правки занимает место текста без прыжка макета - тот же паддинг и шрифт,
   что у самого пузыря, просто с рамкой, обозначающей режим редактирования.
   Высоту ставит код под фактический текст, поэтому своей прокрутки нет. */
.chat-edit-input {
  display: block; width: 100%; box-sizing: border-box;
  font-family: inherit; font-size: 15px; line-height: 1.4;
  background: #fff; color: var(--fg); border: 1px solid var(--accent);
  border-radius: 8px; padding: 4px 6px; resize: none; overflow: hidden;
  /* Поле проявляется, а не возникает рывком: правка - это смена состояния
     сообщения, и она должна читаться как переход, а не как подмена. */
  animation: chatEditIn .2s ease-out both;
}
@keyframes chatEditIn {
  from { opacity: .3; transform: scale(.985); }
  to   { opacity: 1;  transform: none; }
}

.chat-composer { display: flex; gap: 8px; align-items: flex-end; margin-top: 4px; }
.chat-input {
  flex: 1; box-sizing: border-box; padding: 11px 14px; resize: none;
  border: 1px solid var(--border); border-radius: 18px;
  font-family: inherit; font-size: 16px; line-height: 1.35;
  background: var(--bg); color: var(--fg); outline: none;
  max-height: 120px; overflow-y: auto;
}
.chat-input:focus { border-color: var(--pine, #0f9d58); }
.chat-send {
  width: 44px; height: 44px; flex-shrink: 0; border-radius: 50%;
  border: none; background: var(--pine, #0f9d58); color: #fff;
  font-size: 20px; font-weight: 700; cursor: pointer;
}
.chat-send:disabled { opacity: .4; }

.chat-note { font-size: 12px; color: var(--fg-dim); text-align: center; margin-top: 8px; min-height: 16px; }

/* Пока открыт чат, сама страница не прокручивается. Иначе «резиновая» прокрутка
   на телефоне утаскивает всю страницу вместе с прибитыми вкладками и полем ввода -
   именно то, от чего мы уходим. */
body.chat-open { overflow: hidden; }

/* ── Бизнес-инструменты плитками ─────────────────────────────────────────────
   Тот же вид, что на главной: экран перестал ощущаться отдельным приложением.
   Класс .home-tile общий, поэтому всё оформление - углы, значок фоном, появление
   снизу, «дыхание» - приезжает само, дублировать правила не нужно. */

/* Число под названием. У плиток главной подписи статичные, а здесь счётчик
   меняется скриптом - поэтому строка своя, не .home-tile-title. */
.home-tile-sub {
  font-size: 12px; font-weight: 600; color: var(--pine, #0f9d58);
  margin-top: 4px; position: relative; z-index: 2;
}

/* Раньше здесь значок замирал на плитках со счётчиком: движущийся сосед со своим слоем
   мешал iOS дорисовать меняющееся число, и оно обрубалось. Движение вернули - оно есть
   на главной, и раздел не должен от неё отличаться. Обрубленный текст лечится иначе:
   скрипт нудит перерисовку сразу после подстановки числа (см. repaintNudge). */

.tools-note {
  font-size: 13px; color: var(--fg-dim); line-height: 1.45;
  margin: 14px 4px 0;
}

/* Пока инструмент один, он занимает всю ширину: половина плитки рядом с пустотой
   читается как «вторую не загрузили». Появится второй - сетка сама станет в два
   столбца, править ничего не придётся. */
.home-grid .home-tile:only-child { grid-column: span 2; }

/* ── Ответы на сообщения ─────────────────────────────────────────────────────
   На телефоне - свайп вправо по пузырю, на компьютере - кнопка при наведении.
   Двух способов достаточно: длинное нажатие конфликтует с выделением текста,
   а меню по тапу мешало бы читать. */

/* Пузырь ездит за пальцем. transform, а не отступы: отступы пересчитывают
   раскладку каждый кадр и на слабом телефоне жест начинает дёргаться. */
.chat-msg { transition: transform .18s cubic-bezier(.2, .7, .3, 1); }
.chat-msg.is-swiping { transition: none; }

/* Стрелка проступает из-под пузыря по мере свайпа. */
.chat-msg { position: relative; }
.chat-msg::before {
  content: "\21A9";
  position: absolute; left: -30px; top: 50%; transform: translateY(-50%);
  font-size: 16px; color: var(--pine, #0f9d58);
  opacity: 0; transition: opacity .12s; pointer-events: none;
}
.chat-msg.is-mine::before { left: auto; right: -30px; }
.chat-msg.swipe-armed::before { opacity: 1; }

/* Кнопка ответа для компьютера. На телефоне наведения не бывает, поэтому
   она там не появится и место не займёт. */
.chat-reply-btn {
  position: absolute; top: 2px; right: -26px;
  width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-dim);
  font-size: 11px; line-height: 1; cursor: pointer; font-family: inherit;
  opacity: 0; transition: opacity .15s;
}
.chat-msg.is-mine .chat-reply-btn { right: auto; left: -26px; }
.chat-msg:hover .chat-reply-btn { opacity: 1; }
@media (hover: none) { .chat-reply-btn { display: none; } }

/* Цитата внутри пузыря. Полоска слева - тот же приём, что во всех мессенджерах:
   человеку не нужно объяснять, что это ответ. */
.chat-quote {
  border-left: 2px solid var(--pine, #0f9d58);
  padding: 2px 0 2px 8px; margin-bottom: 5px;
  font-size: 13px; line-height: 1.3; cursor: pointer;
}
.chat-quote-who { font-weight: 700; color: var(--pine, #0f9d58); }
.chat-quote-text { color: var(--fg-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-quote.is-deleted .chat-quote-text { font-style: italic; }

/* Оригинал коротко подсвечивается, когда к нему перешли по цитате -
   иначе непонятно, куда именно тебя перенесло. */
@keyframes chat-flash {
  0%, 100% { background: transparent; }
  30%      { background: rgba(15, 157, 88, .16); }
}
.chat-msg.is-flash .chat-bubble { animation: chat-flash 1.1s ease-out; }

/* Панель над полем ввода: на что отвечаем. */
.chat-reply-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; margin-bottom: 6px;
  background: var(--bg); border-radius: 12px;
  border-left: 3px solid var(--pine, #0f9d58);
}
.chat-reply-bar.hidden { display: none; }
.chat-reply-body { flex: 1 1 auto; min-width: 0; }
.chat-reply-who { font-size: 12px; font-weight: 700; color: var(--pine, #0f9d58); }
.chat-reply-text {
  font-size: 13px; color: var(--fg-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-reply-x {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  border: none; background: transparent; color: var(--fg-dim);
  font-size: 19px; line-height: 1; cursor: pointer; font-family: inherit;
}

/* ── ИИ-описание по фото ─────────────────────────────────────────────────────
   Два шага: выбрать фото -> получить описание и слова для поиска.
   Услуга платная, поэтому цена и остаток показываются ДО нажатия, а не после. */

/* Рамка с фото. Высота ограничена: снимок здесь нужен, чтобы убедиться «то самое
   фото загрузилось», а не рассматривать товар - под это хватает трети экрана. */
.desc-drop {
  width: 100%; max-height: 220px; border-radius: 16px;
  background: var(--bg); overflow: hidden; margin-bottom: 10px;
}
.desc-drop.hidden { display: none; }
.desc-preview { width: 100%; max-height: 220px; object-fit: cover; display: block; }

/* Два отдельных выбора «снять / из галереи» убраны: телефон показывает этот выбор
   сам, а лишний шаг заставлял решать то, что система решает лучше. Рамка нажимается
   целиком, пока фото нет - мишень совпадает с надписью. */
.desc-drop { cursor: pointer; }

/* Блок результата. Кнопка копирования справа сверху - человек пришёл сюда
   за текстом, который унесёт в объявление, поэтому копирование главнее чтения. */
.desc-block {
  background: var(--bg); border-radius: 16px;
  padding: 14px 16px; margin-bottom: 12px;
}
.desc-block-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
.desc-block-title { font-size: 13px; font-weight: 700; color: var(--fg-dim); }
.desc-block-body { font-size: 15px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; }

.copy-btn {
  width: 32px; height: 32px; flex: 0 0 auto; border-radius: 9px;
  border: 1px solid var(--border); background: var(--bg);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0;
}
.copy-btn.is-done { border-color: var(--pine, #0f9d58); background: #ecfdf5; }
.copy-ico {
  width: 16px; height: 16px; fill: none;
  stroke: var(--fg-dim); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.copy-btn.is-done .copy-ico { stroke: var(--pine, #0f9d58); }

/* Квадратики превращаются в галочку. Обе фигуры лежат в одном значке и меняются
   местами по классу is-done: одна уезжает с поворотом, вторая приезжает.
   transform-box: fill-box обязателен - иначе поворот считается от угла холста
   SVG и фигура улетает за пределы кнопки. */
.copy-ico .ico-copy, .copy-ico .ico-ok {
  transform-box: fill-box; transform-origin: center;
  transition: opacity .16s ease, transform .28s cubic-bezier(.2, .9, .3, 1.5);
}
.copy-ico .ico-ok { opacity: 0; transform: scale(.3) rotate(-70deg); }
.is-done .copy-ico .ico-copy { opacity: 0; transform: scale(.3) rotate(70deg); }
.is-done .copy-ico .ico-ok { opacity: 1; transform: none; }

@keyframes copyPop {
  0%   { transform: scale(1); }
  38%  { transform: scale(.84); }
  100% { transform: scale(1); }
}
.copy-btn.is-done { animation: copyPop .28s cubic-bezier(.2, .9, .3, 1.4); }

/* Текстовая кнопка копирования подтверждает подписью - значка в ней нет.
   Цвет тот же, чтобы подтверждение читалось одинаково везде. */
.link-btn.is-done {
  border-color: var(--pine, #0f9d58); background: #ecfdf5; color: #065f46;
}

/* Что модель узнала на фото. Показываем над текстом: человек сначала должен
   убедиться, что товар определён верно, и только потом читать описание -
   если распознали не то, читать дальше незачем. */
.desc-found {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  margin-bottom: 10px; padding: 0 2px;
}
.desc-found.hidden { display: none; }
.desc-found-title { font-size: 17px; font-weight: 700; color: var(--fg); }
.desc-found-cat { font-size: 13px; color: var(--fg-dim); }

/* Дата заявки в углу карточки модерации. Без неё непонятно, ждёт человек час
   или третьи сутки - а от этого зависит, кого разбирать первым. */
.admin-person-when {
  margin-left: auto; flex: 0 0 auto; text-align: right;
  font-size: 11.5px; line-height: 1.35; color: var(--fg-dim);
  white-space: pre-line;
}

/* Кнопка заработка на экране описания. Не главное действие, поэтому светлая:
   человек пришёл за описанием, а не за баллами - но выход у него быть должен. */
.desc-earn-btn {
  background: var(--bg); color: var(--pine, #0f9d58);
  border: 1px solid var(--pine, #0f9d58); margin-top: 8px;
}
.desc-earn-btn.hidden { display: none; }

/* ── Кампании: группировка ссылок по компании, направлениям и площадкам ──────── */
.camp-sec-h {
  display: flex; align-items: baseline; justify-content: space-between;
  font-size: 15px; font-weight: 700; color: #111827; margin: 22px 0 8px;
}
.camp-sec-n { font-size: 13px; font-weight: 600; color: #9ca3af; }

.camp-wide-btn { width: 100%; margin-top: 8px; }
/* Кнопка архива идёт сразу за чертой - свой верхний отступ ей не нужен,
   промежуток списка уже есть. */
.earn-list > .camp-sep + .camp-wide-btn { margin-top: 0; }

.camp-row {
  background: #f9fafb; border: 1px solid #eef0f3; border-radius: 14px;
  padding: 12px; margin-bottom: 10px; display: flex; flex-direction: column; gap: 8px;
}
.camp-row-h { display: flex; align-items: center; justify-content: space-between; }
.camp-row-n { font-size: 13px; font-weight: 700; color: #6b7280; }
.camp-row-x {
  border: none; background: transparent; color: #9ca3af;
  font-size: 22px; line-height: 1; padding: 0 4px; cursor: pointer;
}
.camp-row-x:active { color: #dc2626; }
.camp-row .link-input { background: #fff; }
.camp-row .link-input:disabled { background: #f3f4f6; color: #6b7280; }
.camp-row .link-hint { margin-top: 0; }

.camp-mode {
  flex: 1; padding: 11px 8px; border-radius: 12px; border: 1px solid #e5e7eb;
  background: #fff; color: #374151; font-size: 14px; font-weight: 600; cursor: pointer;
}
.camp-mode.is-on { border-color: #0f9d58; background: #ecfdf5; color: #065f46; }
.camp-mode.is-locked { opacity: .65; cursor: default; }

.camp-total {
  margin-top: 18px; padding: 12px 14px; border-radius: 12px;
  background: #ecfdf5; color: #065f46; font-size: 14px; font-weight: 600;
}
.camp-total:empty { display: none; }

.camp-card { border: 1px solid #eef0f3; }
.camp-cpl { margin-top: 10px; font-size: 13px; font-weight: 600; color: #0f9d58; }
.camp-sub { display: block; font-size: 12px; font-weight: 400; color: #9ca3af; margin-top: 2px; }

.camp-link { padding: 10px 0; border-top: 1px solid #f3f4f6; }
.camp-link:first-of-type { border-top: none; }
.camp-link-name { font-size: 14px; font-weight: 600; color: #374151; }

/* Адрес и кнопки в одну строку. Две кнопки во всю ширину под каждой ссылкой
   съедали экран: у площадки с тремя ссылками это шесть широких прямоугольников
   подряд. Край строки рядом с адресом всё равно пустовал - туда их и убрали. */
.camp-link-row { display: flex; align-items: center; gap: 6px; margin-top: 6px; }
.camp-link-row .link-url { flex: 1 1 auto; min-width: 0; margin-top: 0; }
.camp-link-btn { width: 34px; height: 34px; }

/* Черта между действующими площадками и архивом: выше то, что ведут сейчас,
   ниже то, что убрали. Одна линия дешевле заголовка - раздел и так понятен
   по подписи кнопки. */
/* Черта прижата к обеим сторонам. Список идёт с промежутком в 12px между
   карточками, и собственные отступы черты складывались с ним - между последней
   площадкой и архивом получалась дыра в тридцать пикселей на пустом месте.
   Отрицательные поля съедают общий промежуток обратно. */
.camp-sep { height: 1px; background: var(--border); margin: -4px 12px -2px; }
/* В ленте товаров у черты свои отступы: там сетка карточек, а не список
   с общим промежутком, и отрицательные поля сплющили бы её с последним рядом. */
#catalogMySep { margin: 16px 12px 12px; }
#catalogMySep.hidden { display: none; }

/* Номера страниц архива. Ряд прокручивается вбок, а не переносится: перенос
   на второй ряд человек читает как два разных набора страниц. */
.pager-row {
  display: flex; align-items: center; gap: 6px; margin: 14px 4px 4px;
  overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 2px;
}
.pager-row::-webkit-scrollbar { display: none; }
.pager-btn {
  flex: 0 0 auto; min-width: 36px; height: 36px; padding: 0 8px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-dim);
  font-family: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: background .18s ease, border-color .18s ease, color .18s ease,
              transform .18s cubic-bezier(.2, .9, .3, 1.5);
}
.pager-btn:active { transform: scale(.92); }
.pager-btn.is-on {
  border-color: var(--pine, #0f9d58); background: var(--pine, #0f9d58); color: #fff;
}
.pager-gap { flex: 0 0 auto; color: var(--fg-dim); font-size: 14px; padding: 0 2px; }

/* Один блок графика с переключателем. Два блока подряд занимали два экрана
   и заставляли листать, хотя вопросы «растёт ли» и «в какое время заходят»
   человек задаёт по очереди, а не одновременно. */
.chart-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 10px;
}
.chart-tabs { display: flex; gap: 4px; flex: 0 0 auto; }
.chart-tab {
  padding: 5px 10px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-dim);
  font-family: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
  transition: background .18s ease, border-color .18s ease, color .18s ease,
              transform .18s cubic-bezier(.2, .9, .3, 1.5);
}
.chart-tab:active { transform: scale(.93); }
.chart-tab.is-on {
  border-color: var(--pine, #0f9d58); background: #ecfdf5; color: #065f46;
}
.chart-body {
  position: relative;
  /* Вертикальную прокрутку странице оставляем, горизонтальное движение
     забираем себе. Без этого браузер на первом же сдвиге пальца решал,
     что человек листает страницу, и обрывал жест - показания пропадали,
     не успев появиться. */
  touch-action: pan-y;
  animation: desc-in .26s cubic-bezier(.2, .7, .3, 1) both;
}

/* Показания под пальцем. Высоту столбика на глаз в число не переведёшь,
   а именно число человеку и нужно. Наведения на телефоне нет - работаем
   по касанию и по ведению. */
.chart-guide {
  position: absolute; top: 0; bottom: 18px; width: 1px;
  background: var(--pine, #0f9d58); opacity: .45; pointer-events: none;
}
.chart-guide.hidden { display: none; }
/* Тёмная подсказка осталась от графика кампаний, где она всплывает НАД
   линией и должна перебивать её собой. На графике рекламы она лежит в углу
   самой карточки - и чёрный прямоугольник в светлой карточке выглядит
   заплаткой из другого приложения.

   Правило оставлено только для графика кампаний, где оно и родилось. */
#campStatsBody .chart-tip,
.camp-chart .chart-tip {
  position: absolute; top: -6px; z-index: 3; pointer-events: none;
  background: #111827; color: #fff; border-radius: 8px;
  padding: 5px 9px; font-size: 11.5px; line-height: 1.25; white-space: nowrap;
  box-shadow: 0 4px 14px rgba(17, 24, 39, .22);
}
.chart-tip b { font-weight: 700; }
.chart-tip.hidden { display: none; }

/* Плитки статистики. Те, у которых есть ряд по дням, - переключатели линий
   на графике, и это должно быть видно до нажатия: точка сверху и цветная
   рамка во включённом виде. */
/* Плитки под графиком - это ручки управления им, а не главное на экране.
   Поэтому мельче: главное здесь картина, а не сами цифры. По четыре в ряд
   вместо трёх - весь набор влезает в два ряда вместо трёх. */
.tile-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; margin-bottom: 10px;
}
.stat-tile {
  position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 1px; padding: 7px 3px; border-radius: 10px;
  border: 1px solid transparent; background: #f9fafb;
  font-family: inherit; cursor: pointer; text-align: center;
  transition: border-color .18s ease, background .18s ease,
              transform .18s cubic-bezier(.2, .9, .3, 1.5);
}
.stat-tile:active { transform: scale(.95); }
.stat-tile-n {
  font-size: 14px; font-weight: 700; color: #111827; letter-spacing: -.3px;
  white-space: nowrap;
}
.stat-tile-l { font-size: 9.5px; color: var(--fg-dim); line-height: 1.15; }
.stat-tile-dot {
  position: absolute; top: 4px; right: 4px; width: 5px; height: 5px; border-radius: 50%;
  background: var(--tile-line, #0f9d58); opacity: .28;
}
.stat-tile.is-on {
  border-color: var(--tile-line, #0f9d58); background: #fff;
}
.stat-tile.is-on .stat-tile-dot { opacity: 1; }

/* Ряда нет в текущем разрезе - плитка гаснет. Включённая плитка без линии
   на графике читается как поломка, а не как «этих данных тут не бывает». */
.stat-tile.is-off { opacity: .45; }
.stat-tile.is-off .stat-tile-dot { display: none; }

/* Пояснение к цифре без графика. Появляется под плитками по нажатию. */
.tile-help {
  font-size: 12.5px; line-height: 1.45; color: var(--fg-dim);
  background: #f9fafb; border-radius: 10px; padding: 10px 12px; margin-bottom: 10px;
  animation: desc-in .24s cubic-bezier(.2, .7, .3, 1) both;
}
.tile-help.hidden { display: none; }

/* Легенда графика: какая линия что значит. */
/* В легенде стоит и название, и ЗНАЧЕНИЕ. Без пальца - последняя точка ряда,
   с пальцем - та, на которой он стоит. Иначе единственный способ узнать число -
   попасть в точку, а на графике из трёхсот точек это лотерея. */
.chart-legend { display: flex; flex-wrap: wrap; gap: 12px; margin: 8px 0 2px; }
.chart-leg { display: inline-flex; align-items: baseline; gap: 5px; font-size: 12px; color: var(--fg-dim); }
.chart-leg i {
  width: 10px; height: 3px; border-radius: 2px; display: inline-block;
  align-self: center; flex: 0 0 auto;
}
.chart-leg-v {
  font-size: 13px; font-weight: 700; color: var(--fg);
  font-variant-numeric: tabular-nums;
}
/* Пока палец на графике, значения относятся к точке под ним, а не к «сейчас» -
   подсвечиваем, чтобы это не читалось как текущее состояние. */
.chart-legend.is-marked .chart-leg-v { color: var(--pine, #0f9d58); }

/* Время точки - одно на все ряды, стоит первым. Оформлено как метка,
   а не как ряд: у него нет цветной полоски, и спутать его с линией нельзя. */
.chart-leg-time {
  font-size: 12px; font-weight: 700; color: var(--fg);
  background: var(--bg-page, #f5f5f7); border-radius: 999px; padding: 2px 9px;
  font-variant-numeric: tabular-nums;
}
.chart-legend.is-marked .chart-leg-time {
  background: #ecfdf5; color: #065f46;
}

/* Подпись под названием плитки. Отдельно от .home-tile-sub: та зелёная и жирная,
   потому что показывает счётчик - число должно бросаться в глаза. Здесь же обычный
   текст, и зелёным он читался бы как ссылка. */
.home-tile-desc {
  font-size: 11.5px; line-height: 1.35; color: var(--fg-dim);
  margin-top: 4px; position: relative; z-index: 2;
}

/* Кнопки правки и снятия над карточкой размещения. Появляются только у будущих
   постов: идущее и прошедшее трогать нечего - его уже видели, деньги отработаны. */
.ad-mine-acts { display: flex; gap: 6px; margin-left: auto; }
.ad-mine-act {
  width: 28px; height: 28px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-dim);
  font-size: 14px; line-height: 1; cursor: pointer; font-family: inherit;
  display: inline-flex; align-items: center; justify-content: center;
}
.ad-mine-act.is-danger { color: #dc2626; border-color: #fecaca; }

/* Поле для вставки поста. Высокое намеренно: пост в несколько абзацев должен быть
   виден целиком, иначе человек не заметит, что вставил не всё. */
.camp-post {
  min-height: 110px; resize: vertical; line-height: 1.45;
  font-family: inherit; font-size: 14px;
}

/* Готовый текст поста. Моноширинный не нужен, а вот переносы как в оригинале -
   обязательны: человек копирует это целиком и публикует без правок. */
.camp-ready {
  font-size: 13.5px; line-height: 1.5; white-space: pre-wrap;
  color: var(--fg); word-break: break-word;
  background: var(--bg-page, #f5f5f7); border-radius: 10px; padding: 10px 12px;
}

/* Шапка раздела кампаний: «Назад» и «Новая кампания» в одну строку.
   Заголовок убран - название раздела человек только что читал на плитке,
   а место на экране дороже повторения. */
/* Обе кнопки одного размера. У «Назад» свой нижний отступ - внутри этой строки он
   лишний, иначе кнопки стоят на разной высоте и выглядят разными. Метрики берём
   ровно те же, что у .back-btn: шрифт, отступы, скругление. */
.links-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; }
.links-bar .back-btn { margin-bottom: 0; }
.links-new-btn {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 18px 9px 14px; border-radius: 999px;
  border: none; background: var(--pine, #0f9d58); color: #fff;
  font-family: inherit; font-size: 14.5px; font-weight: 600; cursor: pointer;
  white-space: nowrap;
}
.links-new-btn:active { transform: scale(0.95); }
/* Блик на круглой кнопке должен обрезаться по её скруглению, иначе полоса
   света вылезает за края и выглядит артефактом. */
.links-new-btn.btn-shine { border-radius: 999px; overflow: hidden; }

/* Процент под шагом воронки. Мельче числа и серым: главное - сколько человек,
   доля лишь помогает понять, много это или мало. */
.link-step-p { font-size: 11px; color: var(--fg-dim); margin-top: 2px; }

/* Цена результата - самое важное число карточки, поэтому оно крупное и стоит
   отдельно от воронки. Бюджет и переходы человек всё равно делит в уме;
   единственное, что он потом помнит и с чем сравнивает - вот эта цифра. */
.camp-price {
  display: flex; align-items: baseline; gap: 8px;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
}
.camp-price-n { font-size: 22px; font-weight: 700; color: var(--pine, #0f9d58); }
.camp-price-l { font-size: 13px; color: var(--fg-dim); }

/* Название кампании строкой, а не заголовком: на экран уже смотрит кнопка
   «Добавить площадку», и второй крупный элемент рядом спорит с ней за внимание. */
.camp-title-line {
  font-size: 15px; font-weight: 700; color: var(--fg);
  margin: 0 4px 12px; text-align: right;
}

/* Карточка площадки. Крестик и галочка справа от названия - по ним видно,
   вышел пост или нет, не вчитываясь в цифры. */
.place-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.place-name {
  flex: 1 1 auto; min-width: 0; font-size: 14px; font-weight: 700; color: var(--fg);
  word-break: break-all;
}
.place-mark {
  flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700;
}
.place-mark.is-ok { background: #ecfdf5; color: #0f9d58; }
.place-mark.is-no { background: #fef2f2; color: #dc2626; }
.place-stats { display: flex; gap: 6px; }

/* Название кампании строкой с карандашом. Заголовка больше нет: человек только что
   нажал на эту кампанию в списке и помнит, куда зашёл. */
.camp-title-row { display: flex; align-items: center; gap: 8px; margin: 0 4px 10px; }

/* Итог генерации. Появляется мягко и один раз - на любое следующее действие
   внутри кампании экран перерисовывается уже без неё. */
.camp-done {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 0 4px 12px; padding: 11px 13px; border-radius: 12px;
  background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46;
  font-size: 13px; font-weight: 600; line-height: 1.4;
  animation: campDoneIn .42s cubic-bezier(.2, .9, .3, 1.2) both;
}
.camp-done-ico {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%;
  background: var(--pine, #0f9d58); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700;
  animation: campDonePop .5s cubic-bezier(.2, .9, .3, 1.6) .12s both;
}
@keyframes campDoneIn {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes campDonePop {
  0%   { transform: scale(0) rotate(-90deg); }
  60%  { transform: scale(1.18) rotate(0deg); }
  100% { transform: scale(1); }
}
.camp-title-txt { flex: 1 1 auto; font-size: 15px; font-weight: 700; color: var(--fg); }
.camp-title-edit {
  flex: 0 0 auto; width: 28px; height: 28px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-dim);
  font-size: 13px; cursor: pointer; font-family: inherit;
}

/* Дата публикации со значком состояния - одной кнопкой рядом с названием. Отдельная
   строка ввода занимала место ради значения, которое ставят один раз и не трогают.
   Поле datetime-local спрятано: оно нужно только чтобы открыть системный выбор даты. */
.place-when {
  flex: 0 0 auto; padding: 5px 10px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-dim);
  font-family: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
  white-space: nowrap;
}
.place-when.is-ok { border-color: #a7f3d0; background: #ecfdf5; color: #065f46; }
.place-when.is-late { border-color: #fecaca; background: #fef2f2; color: #b91c1c; }
.place-when.is-wait { color: var(--fg); }
/* Панель настроек площадки. Раскрывается по нажатию на дату: настройки трогают
   один раз, а место занимали бы постоянно. */
.place-panel {
  margin: 10px 0 4px; padding: 12px; border-radius: 12px;
  background: var(--bg-page, #f5f5f7);
  animation: desc-in .3s cubic-bezier(.2, .7, .3, 1) both;
}
.place-panel.hidden { display: none; }

/* Подтверждение публикации - в самой карточке. Системное окно всплывало поверх
   всего, выглядело чужим и забирало фокус, после чего экран перерисовывался
   и прокрутка улетала наверх. */
.place-confirm {
  margin: 10px 0 4px; padding: 12px; border-radius: 12px;
  background: #ecfdf5; border: 1px solid #a7f3d0;
  animation: desc-in .28s cubic-bezier(.2, .7, .3, 1) both;
}
.place-confirm.hidden { display: none; }
.place-confirm-t { font-size: 13px; line-height: 1.45; color: #065f46; margin-bottom: 10px; }
.place-confirm-a { display: flex; gap: 8px; }
.place-panel-row { display: flex; gap: 8px; }
/* Поля панели - в столбик и во всю ширину. Раньше они стояли парами с зелёной
   кнопкой сохранения сбоку: три «ОК» подряд читались как форма, а не как правка
   одной карточки. */
.place-panel-in { width: 100%; margin-bottom: 8px; }
.place-panel .camp-when-box { margin: 0 0 4px; }

/* Часы - метка состояния, а не кнопка: правка открывается только карандашом. */
.place-when { cursor: default; }
.place-in {
  flex: 1 1 0; min-width: 0; padding: 9px 10px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  font-family: inherit; font-size: 14px;
}
/* Контакт админа - строкой в самом низу карточки, рядом со ссылками этой же
   площадки. Поле и кнопка в одну линию: заполняют один раз, пишут регулярно. */
.place-adm-row { display: flex; gap: 8px; margin-top: 12px; }
.place-adm-in { flex: 1 1 auto; min-width: 0; }
.place-write {
  flex: 0 0 auto; padding: 9px 14px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  font-family: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  white-space: nowrap;
}

/* Название площадки стало кнопкой - по нему открывается статистика. Выглядит как
   текст: подчёркивание и цвет ссылки в карточке со своими цифрами были бы шумом. */
/* Название одной строкой с многоточием. break-all рвал адрес по буквам, и при
   узком месте он превращался в столбик из отдельных знаков. */
.place-name {
  flex: 1 1 auto; min-width: 0; text-align: left; padding: 0;
  border: none; background: none; cursor: pointer;
  font-family: inherit; font-size: 14px; font-weight: 700; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Кнопки отдельной строкой под названием: вчетвером в одной строке с именем
   они не оставляли ему места. */
.place-tools {
  display: flex; align-items: center; gap: 6px;
  margin: 8px 0 10px; flex-wrap: wrap;
}
.place-tools .place-when { margin-right: auto; }
.place-edit {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg);
  cursor: pointer; font-family: inherit; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.place-edit-ico {
  width: 17px; height: 17px; fill: none; stroke: var(--fg-dim);
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.place-edit.is-on { background: var(--pine, #0f9d58); border-color: var(--pine, #0f9d58); }
.place-edit.is-on .place-edit-ico { stroke: #fff; }

/* Карандаш превращается в галочку. Обе фигуры лежат в одном значке и меняются
   местами по классу is-on: подмена картинки браузеру анимировать нечем, а тут
   есть что - одна уезжает с поворотом, вторая приезжает.
   transform-box нужен, чтобы поворот считался от самой фигуры, а не от угла
   холста SVG - иначе значок улетает за пределы кнопки. */
.edit-morph .ico-pen, .edit-morph .ico-ok {
  transform-box: fill-box; transform-origin: center;
  transition: opacity .16s ease, transform .28s cubic-bezier(.2, .9, .3, 1.5);
}
.edit-morph .ico-ok { opacity: 0; transform: scale(.3) rotate(-70deg); }
.is-on .edit-morph .ico-pen { opacity: 0; transform: scale(.3) rotate(70deg); }
.is-on .edit-morph .ico-ok { opacity: 1; transform: none; }

/* Короткий щелчок самой кнопки в момент переключения: без него превращение
   выглядит как самопроизвольная смена значка, а не как ответ на нажатие. */
@keyframes editPop {
  0%   { transform: scale(1); }
  38%  { transform: scale(.84); }
  100% { transform: scale(1); }
}
.place-edit.is-pop { animation: editPop .28s cubic-bezier(.2, .9, .3, 1.4); }

/* Карандаш у названия кампании теперь такая же кнопка, как в списке. */
.place-edit.camp-title-edit { width: 30px; height: 30px; font-size: 0; }

/* Стрелка разворота - крайняя справа. В свёрнутом виде карточка показывает только
   то, по чему сравнивают площадки: имя, время, клики, цену, расход. Готовый пост,
   ссылки и контакт нужны реже, и платить за них высотой экрана не надо. */
.place-chev {
  flex: 0 0 auto; width: 30px; height: 34px; border: none; background: none;
  color: var(--fg-dim); font-size: 22px; line-height: 1; cursor: pointer;
  font-family: inherit; padding: 0;
  transition: transform .2s;
}
.place-chev.is-open { transform: rotate(90deg); }
.place-more { animation: desc-in .3s cubic-bezier(.2, .7, .3, 1) both; }
.place-more.hidden { display: none; }

/* Что распознали на фото. Стоит ДО кнопки оплаты: человек должен убедиться,
   что это его товар, прежде чем платить. Списать деньги за чужой товар -
   худший способ потерять человека навсегда. */
.desc-reco {
  background: #ecfdf5; border-radius: 14px; padding: 12px 14px; margin-bottom: 10px;
}
.desc-reco.hidden { display: none; }
.desc-reco-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.desc-reco-row + .desc-reco-row { margin-top: 6px; padding-top: 6px; border-top: 1px solid #d1fae5; }
.desc-reco-l { font-size: 11px; color: #065f46; flex: 0 0 auto; }
/* Мельче и с переносом. Название товара от модели длинное - «Носки мужские
   серые в рубчик хлопок», - и прежним кеглем оно обрезалось на полуслове. */
.desc-reco-v {
  font-size: 12.5px; font-weight: 700; color: #06301f; line-height: 1.3;
  text-align: right; word-break: break-word; min-width: 0;
}

/* «Заменить фото» после загрузки уходит на второй план: главное действие теперь
   ниже, и две одинаково яркие кнопки спорили бы за нажатие. */
.desc-pick-secondary {
  background: var(--bg); color: var(--fg-dim);
  border: 1px solid var(--border);
}

/* Вводная строка. Не серый хвостик под кнопкой, а нормальный текст сверху:
   человек сначала должен понять, что тут вообще происходит, и только потом
   увидеть кнопку. */
.desc-lead {
  font-size: 14px; line-height: 1.5; color: var(--fg-dim);
  margin: 0 2px 14px;
}
.desc-lead.hidden { display: none; }

/* Разделитель между «дать фото» и «получить описание». Два действия подряд
   без границы читались как одно, и серая кнопка выглядела поломкой первой. */
.desc-divider { height: 1px; background: var(--border); margin: 18px 2px 16px; }

/* Дышим: кнопки на этом экране стояли впритык друг к другу. */
#descStep1 .submit-btn { margin-top: 0; }
#descStep2Box .submit-btn { margin-top: 2px; }
#descStep2Box .link-hint { margin-top: 8px; }

/* Живые статусы, пока модель смотрит фото. Несколько секунд немого экрана человек
   читает как «зависло» - строка со сменяющимся текстом отвечает на вопрос
   «оно вообще работает» раньше, чем он успеет его задать. */
.desc-status {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-radius: 14px;
  background: var(--bg); margin-top: 10px;
}
.desc-status.hidden { display: none; }

/* Подсказка под кнопкой держит свою высоту, даже когда пуста. Иначе её очистка
   укорачивает страницу, браузер прижимает прокрутку ко дну - и экран прыгает
   вверх ровно в момент нажатия на кнопку. */
#descHint { min-height: 1.35em; }

/* Оценка описания: два больших пальца под результатом. Крупные - в них целятся
   одним движением, не глядя, сразу после чтения текста. */
.desc-rate {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 4px 0 12px; padding: 10px 14px; border-radius: 14px;
  background: var(--bg);
  transition: opacity .3s ease, transform .3s ease;
}
.desc-rate.is-done { opacity: 0; transform: translateY(-6px); }
.desc-rate.hidden { display: none; }
.desc-rate-q { font-size: 13.5px; color: var(--fg-dim); }
.desc-rate-btns { display: flex; gap: 8px; flex: 0 0 auto; }
.desc-rate-btn {
  width: 44px; height: 40px; border-radius: 12px; font-size: 21px; line-height: 1;
  border: 1px solid var(--border); background: var(--card); cursor: pointer;
  transition: transform .18s cubic-bezier(.2, .9, .3, 1.5), border-color .18s ease;
}
.desc-rate-btn:active { transform: scale(.9); }

/* Всплывающее сообщение по центру. Живёт поверх всего и НЕ двигает вёрстку:
   строка, вставленная в поток, сдвинула бы текст, который человек читает. */
.toast {
  position: fixed; left: 50%; top: 50%; z-index: 120;
  transform: translate(-50%, -50%);
  background: rgba(17, 24, 39, .92); color: #fff;
  padding: 14px 20px; border-radius: 14px;
  font-size: 15px; font-weight: 600; text-align: center; max-width: 80vw;
  box-shadow: 0 14px 40px rgba(17, 24, 39, .3);
  animation: toastIn .34s cubic-bezier(.2, .9, .3, 1.5) both;
  pointer-events: none;
}
.toast.is-out { animation: toastOut .3s cubic-bezier(.4, 0, .8, .4) both; }
@keyframes toastIn {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.7); }
  60%  { opacity: 1; transform: translate(-50%, -50%) scale(1.06); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes toastOut {
  from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  to   { opacity: 0; transform: translate(-50%, -130%) scale(.9); }
}

/* Пояснение под вопросом в форме отзыва. */
.feedback-lead { font-size: 13.5px; color: var(--fg-dim); margin-bottom: 10px; }
.feedback-lead.hidden { display: none; }

/* Уход формы за экран после отправки. */
#feedbackOverlay.is-out .modal-card {
  animation: sheetOut .3s cubic-bezier(.4, 0, .8, .4) both;
}
@keyframes sheetOut {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(60px) scale(.97); }
}
/* Спокойный вращающийся ободок вместо пульсирующей точки: точка прыгала в размере
   и на фоне сменяющегося текста получалось два движения сразу - глазу не за что
   зацепиться, и всё вместе читалось как дёрганье. */
.desc-status-dot {
  width: 15px; height: 15px; border-radius: 50%; flex: 0 0 auto;
  border: 2px solid var(--border);
  border-top-color: var(--pine, #0f9d58);
  animation: desc-spin .8s linear infinite;
}
@keyframes desc-spin { to { transform: rotate(360deg); } }
/* Строка статуса. Высота держится минимум в одну строку, чтобы блок
   не подпрыгивал, когда фраза короче предыдущей. min-width: 0 обязателен -
   без него длинная фраза в flex-строке не переносится, а вылезает наружу
   и наезжает на соседей. */
.desc-status-txt {
  flex: 1 1 auto; min-width: 0;
  font-size: 14px; color: var(--fg); line-height: 1.35; min-height: 1.35em;
}
/* Новая фраза въезжает снизу. Анимация на самом элементе, без отложенной
   подмены текста - двум фразам физически негде наложиться. */
.desc-status-txt.is-in { animation: descLineIn .3s cubic-bezier(.2, .8, .3, 1) both; }
@keyframes descLineIn {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .desc-status-txt.is-in { animation: none; }
}

/* Снимок нажимается - это и есть замена фото. */
.desc-drop { cursor: pointer; }

/* ── Появление на экране описания ────────────────────────────────────────────
   Блоки приезжают снизу с лёгким запозданием друг за другом. Одновременное
   появление читается как перерисовка страницы, последовательное - как ответ
   системы на действие. */
@keyframes desc-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

#descStep1 .desc-drop,
#descStep1 .desc-status,
#descStep2Box { animation: desc-in .45s cubic-bezier(.2, .7, .3, 1) both; }

/* Снимок появляется чуть медленнее и с приближением: он главный на экране,
   и резкий скачок картинки в полэкрана бьёт по глазам сильнее всего. */
.desc-drop { animation: desc-photo-in .55s cubic-bezier(.2, .7, .3, 1) both; }
@keyframes desc-photo-in {
  from { opacity: 0; transform: translateY(18px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Само изображение проявляется отдельно, уже внутри рамки: пока браузер
   раскодирует снимок, рамка успевает встать на место, и в ней не мигает
   белый прямоугольник. */
.desc-preview { animation: desc-fade .5s ease-out both; animation-delay: .1s; }
@keyframes desc-fade { from { opacity: 0; } to { opacity: 1; } }

/* Строчки распознанного - друг за другом, чтобы взгляд шёл сверху вниз. */
.desc-reco-row { animation: desc-in .4s cubic-bezier(.2, .7, .3, 1) both; }
.desc-reco-row:nth-child(1) { animation-delay: .05s; }
.desc-reco-row:nth-child(2) { animation-delay: .13s; }

/* Кнопка и подпись под ней - последними: сначала человек читает, ЧТО распознали,
   и только потом видит, за что платит. */
#descStep2Box .submit-btn { animation: desc-in .4s cubic-bezier(.2, .7, .3, 1) both; animation-delay: .18s; }
#descStep2Box .link-hint { animation: desc-fade .4s ease-out both; animation-delay: .26s; }

/* Блоки результата - тем же движением, с нарастающей задержкой. */
#descStep2 .desc-block { animation: desc-in .45s cubic-bezier(.2, .7, .3, 1) both; }
#descStep2 .desc-block:nth-of-type(1) { animation-delay: .04s; }
#descStep2 .desc-block:nth-of-type(2) { animation-delay: .14s; }
#descStep2 .desc-found { animation: desc-in .4s cubic-bezier(.2, .7, .3, 1) both; }

/* Кому движение мешает - его быть не должно. */
@media (prefers-reduced-motion: reduce) {
  #descStep1 .desc-drop, #descStep1 .desc-status, #descStep2Box,
  .desc-preview, .desc-reco-row, #descStep2Box .submit-btn,
  #descStep2Box .link-hint, #descStep2 .desc-block, #descStep2 .desc-found {
    animation: none;
  }
  .desc-status-dot { animation: desc-spin 1.6s linear infinite; }
}

/* Название - поле ввода, но выглядит как значение: рамка и фон появляются только
   при касании. Явное поле посреди карточки читалось бы как «заполни сам»,
   а система его уже заполнила - надо лишь дать возможность поправить. */
.desc-reco-in {
  flex: 1 1 auto; min-width: 0; width: 100%; text-align: right;
  border: 1px solid transparent; border-radius: 8px;
  background: transparent; padding: 3px 6px;
  font-family: inherit; font-size: 12.5px; font-weight: 700; color: #06301f;
  /* Поле - одна строка, поэтому длинное название всё равно упирается в край.
     Многоточие честнее обрыва: видно, что текст продолжается. */
  text-overflow: ellipsis;
}
.desc-reco-in:focus {
  outline: none; border-color: #6ee7b7; background: #fff; text-align: left;
}

/* Квадратная галочка справа от времени. Панель сохраняет по нажатию, а не по
   каждому изменению поля: раньше она перерисовывалась сразу после выбора даты
   и закрывалась прежде, чем человек успевал поставить время. */
.place-ok {
  flex: 0 0 auto; width: 42px; align-self: stretch;
  border-radius: 9px; border: none;
  background: var(--pine, #0f9d58); color: #fff;
  font-size: 17px; font-weight: 700; cursor: pointer; font-family: inherit;
}
.place-ok:active { transform: scale(.95); }

/* Карандаш в нажатом состоянии - видно, что карточка открыта на правку.
   Без этого человек не понимает, почему поля вдруг стали доступны. */
.place-edit.is-on {
  background: var(--pine, #0f9d58); border-color: var(--pine, #0f9d58); color: #fff;
}

/* Закрытое поле контакта выглядит как текст, а не как приглашение печатать:
   правка начинается с карандаша, и поле не должно звать до него. */
.place-adm-in[readonly] {
  border-color: transparent; background: transparent; color: var(--fg);
  padding-left: 0;
}
.place-adm-in[readonly]::placeholder { color: var(--fg-dim); }

/* Заголовок готового поста - кнопка сворачивания. Развёрнутый пост занимал экран
   целиком, и чтобы добраться до ссылок или контакта, приходилось листать мимо
   текста, который человек сам же и написал. */
.camp-ready-head {
  display: flex; align-items: center; gap: 8px; width: 100%;
  margin-top: 12px; padding: 10px 0; border: none; background: none;
  cursor: pointer; font-family: inherit; text-align: left;
}
.camp-ready-chev {
  flex: 0 0 auto; font-size: 18px; line-height: 1; color: var(--fg-dim);
  transition: transform .2s;
}
.camp-ready-head.is-open .camp-ready-chev { transform: rotate(90deg); }
.camp-ready-head .desc-block-title { flex: 1 1 auto; }
.camp-ready.hidden { display: none; }

/* Поле переименования площадки - на месте самого названия: правка происходит
   там, где человек на него смотрит, а не в отдельной панели ниже. */
.place-name-in {
  flex: 1 1 auto; min-width: 0;
  font-size: 14px; font-weight: 700; color: var(--fg);
}
/* .place-adm-ok удалён: контакт сохраняется сам, кнопки подтверждения нет. */

/* Название кампании с кнопками правки и удаления. Правка живёт в общем списке:
   имя человек видит именно здесь и по нему сравнивает кампании между собой. */
.camp-card-head { display: flex; align-items: center; gap: 8px; }
.camp-card-head .link-card-title { flex: 1 1 auto; min-width: 0; }
.camp-card-btn { width: 30px; height: 30px; }
.camp-card-btn.is-danger { color: #dc2626; border-color: #fecaca; }
/* Переименование идёт ПРЯМО В СТРОКЕ названия, поэтому поле не должно выглядеть
   полем: рамка с фоном читается как всплывшее окно правки, хотя ничего не всплывало.
   Оставляем ровно тот же кегль и насыщенность, что у названия, и тонкую линию снизу -
   единственный признак, что текст стал редактируемым. */
/* Селектор с типом обязателен. Базовое правило input[type="text"] весом 0-1-1
   сильнее любого одиночного класса, поэтому прежний .camp-name-in проигрывал ему
   рамку, фон, скругление и отступы - поле снова выглядело всплывшим окном,
   хотя правило вроде бы было написано. Классу нужен тот же тип в селекторе,
   чтобы перевесить. */
input[type="text"].camp-name-in {
  flex: 1 1 auto; min-width: 0; width: auto;
  font-size: 16px; font-weight: 700; color: var(--fg);
  padding: 1px 0; border: 0; border-radius: 0; background: transparent;
  box-shadow: none; outline: none;
  border-bottom: 1px solid var(--border);
  transition: border-bottom-color .18s ease;
}
input[type="text"].camp-name-in:focus {
  border-color: transparent; border-bottom-color: var(--pine, #0f9d58);
}
.camp-title-row input[type="text"].camp-name-in { font-size: 15px; }

/* Дата и время площадки в форме - в одну строку: два поля рядом занимают
   столько же, сколько одно, а разнесённые по строкам растягивают форму. */
.camp-p-when { display: flex; gap: 8px; }
.camp-p-when .link-input { flex: 1 1 0; min-width: 0; }

/* Дата публикации готовыми ответами. Системный календарь просит выбрать точный
   день, хотя почти всегда это сегодня, завтра или послезавтра - и мелкую сетку
   чисел на телефоне ещё надо не промазать. Точный выбор остался под «Другой». */
.camp-when-box {
  margin-top: 10px; padding: 10px; border-radius: 12px;
  background: var(--bg-page, #f5f5f7);
}

/* Подпись над рядами. Без неё два ряда кнопок читались как один ворох:
   непонятно, где день, а где время. */
.camp-when-h {
  font-size: 11.5px; font-weight: 600; color: var(--fg-dim);
  margin: 0 2px 7px; letter-spacing: .2px;
}

/* Дата и время - двумя полями в одну строку. Кнопок «Сегодня/Завтра» больше
   нет: они закрывали собой то, ради чего человек сюда пришёл - выбрать
   конкретный день. */
/* Одна кнопка вместо двух системных полей: за ней барабаны. */
.camp-when-row { display: flex; }
.when-btn {
  flex: 1 1 auto; width: 100%; padding: 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--card); color: var(--fg-dim);
  font-family: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
  transition: border-color .18s ease, color .18s ease, transform .18s ease;
}
.when-btn:active { transform: scale(.985); }
.when-btn.is-set { color: var(--fg); border-color: #a7f3d0; background: #f7fdfa; }

/* Барабаны. Месяц и год - подпись сверху, а не колесо: их тут никто не крутит,
   они меняются сами, когда день переходит через конец месяца. */
.sheet-card { max-width: 320px; }
.sheet-head {
  text-align: center; font-size: 15px; font-weight: 700; color: var(--fg);
  margin-bottom: 10px;
}
/* Подписи над барабанами: где дата, где время. Три колонки цифр без них
   читаются как одно длинное число. */
.wheel-caps {
  display: flex; justify-content: center; gap: 4px; padding: 0 4px 4px;
}
.wheel-cap {
  font-size: 11px; font-weight: 600; color: var(--fg-dim);
  text-align: center; letter-spacing: .2px;
}
.wheel-cap-day { width: 68px; }
.wheel-cap-time { width: 152px; }

.wheel-rows {
  position: relative; display: flex; justify-content: center; gap: 4px;
  padding: 0 4px 6px;
}

/* Черта между датой и временем - тонкая, но по всей высоте: две группы должны
   читаться как две, а не как три равнозначные колонки. */
.wheel-divider {
  z-index: 2; width: 1px; align-self: stretch; margin: 8px 6px;
  background: var(--border);
}
.wheel {
  position: relative; z-index: 2;
  height: 190px; width: 68px; overflow-y: auto;
  scroll-snap-type: y mandatory; -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* Маска по краям: строки уходят в туман, и видно, что список крутится
     дальше, а не обрывается. */
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 26%, #000 74%, transparent);
          mask-image: linear-gradient(180deg, transparent, #000 26%, #000 74%, transparent);
}
.wheel::-webkit-scrollbar { display: none; }
.wheel-inner { padding: 76px 0; }
.wheel-item {
  height: 38px; line-height: 38px; text-align: center;
  scroll-snap-align: center;
  font-size: 19px; color: var(--fg-dim); font-variant-numeric: tabular-nums;
  transition: color .15s ease, transform .15s ease;
}
.wheel-item.is-on { color: var(--fg); font-weight: 700; transform: scale(1.12); }
.wheel-sep {
  z-index: 2; align-self: center; font-size: 19px; font-weight: 700;
  color: var(--fg-dim); padding-bottom: 2px;
}
/* Две полоски по центру вместо заливки: выбранная строка остаётся на белом,
   как в системных барабанах, и читается лучше - заливка приглушала цифру,
   ради которой всё и крутят. */
.wheel-band {
  position: absolute; left: 4px; right: 4px; top: 50%; height: 38px;
  transform: translateY(-50%); z-index: 1; pointer-events: none;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
@keyframes whenSlide {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* Решение словами. «04.09.2026 / 12:30» человек переводит в голове, а тут
   читает готовое: выйдет завтра в 14:00. */
/* Строки-пересказа под кнопкой больше нет: она повторяла то, что написано
   на самой кнопке. Подскок при выборе переехал на кнопку. */
.when-btn.is-new { animation: whenPop .34s cubic-bezier(.2, .9, .3, 1.4) both; }
@keyframes whenPop {
  0%   { opacity: .4; transform: translateY(-4px) scale(.98); }
  60%  { opacity: 1;  transform: translateY(0) scale(1.02); }
  100% { opacity: 1;  transform: none; }
}

/* Найденные в посте ссылки - просто список. Настройки направления человеку не нужны:
   адрес взят из его же поста и вести должен туда же, куда вёл. */
.camp-found { margin-top: 10px; }
.camp-found-row {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; margin-bottom: 6px; border-radius: 10px;
  background: #ecfdf5;
}
.camp-found-ico { flex: 0 0 auto; font-size: 14px; }
.camp-found-url {
  flex: 1 1 auto; min-width: 0; font-size: 13px; color: #065f46;
  word-break: break-all;
}
.camp-found-go { flex: 0 0 auto; font-size: 13px; color: #059669; }

/* Показанный адрес человек первым делом пытается нажать. Раз он нажимается -
   это должно быть видно до нажатия: подчёркивание и отклик на касание. */
.camp-found-row.is-tap, .link-url.is-tap { cursor: pointer; }
.camp-found-row.is-tap:active, .link-url.is-tap:active { opacity: .65; }
.camp-found-row.is-tap .camp-found-url { text-decoration: underline; }
.link-url.is-tap { text-decoration: underline; }

/* Кнопка проверки публикации. Обычный запрос к веб-превью канала, платные модели
   не участвуют - поэтому нажимать можно сколько угодно. */
.place-check {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg);
  font-size: 15px; line-height: 1; cursor: pointer; font-family: inherit; padding: 0;
}
.place-check.is-busy { opacity: .5; }

/* Звезда «в избранное». Стоит рядом с цифрами площадки намеренно: отмечают её
   по итогу размещения, глядя на цену перехода, а не в отдельном меню. */
.place-star {
  flex: 0 0 auto; width: 30px; height: 30px; border: none; background: none;
  color: #d1d5db; font-size: 19px; line-height: 1; cursor: pointer; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color .2s ease, transform .2s cubic-bezier(.2, .9, .3, 1.5);
}
.place-star:active { transform: scale(.88); }
.place-star.is-on { color: #f0b429; }
@keyframes starPop {
  0%   { transform: scale(1) rotate(0deg); }
  45%  { transform: scale(1.35) rotate(18deg); }
  100% { transform: scale(1) rotate(0deg); }
}
.place-star.is-pop { animation: starPop .42s cubic-bezier(.2, .9, .3, 1.5); }

/* Канал или группа. У группы счётчика просмотров не существует, и знать это
   надо ДО размещения - при выборе, а не после оплаты. */
.place-tag {
  flex: 0 0 auto; padding: 2px 7px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg-page, #f5f5f7);
  color: var(--fg-dim); font-size: 10.5px; font-weight: 600; white-space: nowrap;
}
.place-tag.is-ok { border-color: #a7f3d0; background: #ecfdf5; color: #065f46; }

/* Своё окно подтверждения. Системное выглядит чужим, на разных клиентах
   Telegram рисуется по-разному и забирает фокус - после него экран
   перерисовывается сам собой. */
.ask-back {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center; padding: 22px;
  background: rgba(17, 24, 39, .42);
  animation: askIn .18s ease both;
}
.ask-back.is-out { animation: askOut .18s ease both; }
/* Прокрутка внутри самого окна остаётся - длинная форма должна листаться.
   Наружу жест не уходит: overscroll-behavior не даёт странице под окном
   подхватить движение, когда список внутри уже упёрся в край. */
.ask-back, .modal-overlay { overscroll-behavior: contain; touch-action: none; }
.ask-card, .modal-card { touch-action: auto; overscroll-behavior: contain; }
.ask-card {
  width: 100%; max-width: 340px; background: var(--card, #fff);
  border-radius: 16px; padding: 18px 16px 14px;
  box-shadow: 0 18px 48px rgba(17, 24, 39, .26);
  animation: askCard .24s cubic-bezier(.2, .9, .3, 1.3) both;
}
.ask-title { font-size: 16px; font-weight: 700; color: var(--fg); margin-bottom: 7px; }
.ask-text { font-size: 13.5px; line-height: 1.45; color: var(--fg-dim); margin-bottom: 14px; }
.ask-acts { display: flex; gap: 8px; }
.ask-acts .link-btn { padding: 11px; }
.ask-acts .link-btn-main.is-danger { background: #dc2626; border-color: #dc2626; }
.ask-input { margin-bottom: 14px; }
/* Меню действий - кнопки в столбик: горизонтально три подписи не помещаются
   и режутся посередине слова. */
.ask-menu { display: flex; flex-direction: column; gap: 8px; }
.ask-menu-btn { width: 100%; padding: 12px; }
.ask-menu-btn.is-danger { color: #dc2626; border-color: #fecaca; }
@keyframes askIn  { from { opacity: 0; } to { opacity: 1; } }
@keyframes askOut { from { opacity: 1; } to { opacity: 0; } }
@keyframes askCard {
  from { opacity: 0; transform: translateY(12px) scale(.96); }
  to   { opacity: 1; transform: none; }
}
/* Кнопка отметки в раскрытом состоянии: видно, что вопрос задан именно ей. */
.place-check.is-on {
  border-color: var(--pine, #0f9d58); background: #ecfdf5; color: #065f46;
}

/* Подтверждение сохранения строкой, а не всплывающим окном: окно требовало
   нажать «Закрыть» ради сообщения, которое и так видно по обновившемуся тексту,
   и выбрасывало экран наверх - проверить результат было негде. */
.link-hint.is-ok { color: var(--pine, #0f9d58); font-weight: 600; }


/* Четыре плитки в ряду: скорость встала рядом с остальными цифрами. Шрифт
   мельче, чтобы «1 500 ₽» не переносилось. */
.place-stats .link-step { flex: 1 1 0; min-width: 0; padding: 8px 4px; }
.place-stats .link-step-n { font-size: 15px; }
.place-stats .link-step-l { font-size: 10px; }


/* Запрет объясняем, а не прячем: молчаливое отсутствие кнопки человек читает
   как поломку, а не как правило. */
.camp-wide-btn:disabled { opacity: .55; cursor: default; font-size: 13px; }

/* Запертый текст: видно, что это запись о том, что уже опубликовано,
   а не поле для ввода. */
.camp-post.is-locked {
  background: var(--bg-page, #f5f5f7); color: var(--fg-dim);
  border-color: transparent;
}

/* Поле поста не меняет высоту при наборе: растущее поле сдвигает всё под собой
   на каждом символе, и экран дёргается прямо под пальцами. */
.camp-post.is-fixed { height: 170px; resize: none; overflow-y: auto; }

/* Запертые поля карточки: видно, что это запись о том, что уже произошло,
   а не место для ввода. */
.place-in:disabled {
  background: transparent; border-color: transparent;
  color: var(--fg-dim); -webkit-text-fill-color: var(--fg-dim); opacity: 1;
}


/* ═══ Строка «Назад + название» ══════════════════════════════════════════════
   Правила стоят В КОНЦЕ файла намеренно. Первые попытки я вписал рядом
   с остальным оформлением заголовков - выше по файлу, чем базовые .feed-view
   и .projects-heading. Базовые шли следом и перекрывали их: заголовок оставался
   у левого края, а верхнее поле экрана возвращалось. Здесь перекрывать нечем. */
.feed-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; width: 100%; margin: 0 0 8px;
}
.feed-bar > .back-btn { margin: 0; flex: 0 0 auto; }
/* Название прижато вправо распоркой между двумя элементами строки, а не
   выравниванием текста: так оно стоит у края при любой ширине экрана
   и не зависит от того, растянулся блок или нет. */
.feed-bar > .projects-heading {
  margin: 0; flex: 0 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 6px;
  text-align: right; line-height: 1.25;
}

/* Верхние поля экранов. Над ними уже есть отступ шапки приложения, ещё
   двенадцать пикселей сверху - пустая полоса на каждом экране. */
.feed-view { padding-top: 4px; }
.news-view { padding-top: 2px; }
.feed-view .filter-bar { margin-bottom: 10px; }
.screen-topbar { margin-bottom: 8px; }


/* ═══ Одинаковая шапка на ВСЕХ экранах ═══════════════════════════════════════
   Панелей исторически три вида - feed-bar, screen-topbar, links-bar, - и каждая
   жила по своим правилам: где-то содержимое расползалось по центру, где-то
   у кнопки оставался лишний отступ снизу, где-то экран добавлял своё верхнее
   поле поверх отступа шапки приложения.
   Здесь они сведены к одному поведению: «Назад» вплотную к левому верхнему
   углу, всё остальное - вплотную к правому. */
.feed-bar,
.screen-topbar,
.links-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  margin: 0 0 8px;
}

/* Кнопка возврата ничего вокруг себя не занимает: её собственный отступ снизу
   складывался с отступом панели и давал двойной провал под строкой. */
.feed-bar > .back-btn,
.screen-topbar > .back-btn,
.links-bar > .back-btn {
  margin: 0;
  flex: 0 0 auto;
}

/* Всё, что напротив кнопки, прижато к правому краю распоркой. Авто-отступ
   слева нужен для случаев, когда элементов в строке больше двух: тогда
   распорка растащила бы их поровну, а нам нужна плотная группа справа. */
.feed-bar > *:not(.back-btn),
.screen-topbar > *:not(.back-btn),
.links-bar > *:not(.back-btn) {
  flex: 0 1 auto;
  min-width: 0;
}
.feed-bar > *:nth-child(2),
.screen-topbar > *:nth-child(2),
.links-bar > *:nth-child(2) {
  margin-left: auto;
}

/* Заголовок в такой строке - обычный текст, а не блок с полями. */
.feed-bar > .projects-heading,
.screen-topbar > .projects-heading,
.links-bar > .projects-heading {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  text-align: right;
  line-height: 1.25;
}

/* Верхние поля экранов. Над ними уже есть отступ шапки приложения - второй
   давал пустую полосу высотой с карточку на каждом экране. */
main { padding-top: 8px; }
.feed-view { padding-top: 4px; }
.news-view { padding-top: 2px; }
.chat-view { padding-top: 0; }
.feed-view .filter-bar { margin-bottom: 10px; }


/* В ЧАТЕ шапка приложения при клавиатуре не прячется. На остальных экранах
   прятать её полезно - форма длинная, каждая строка на счету. В чате наоборот:
   вместе с шапкой уезжали вкладки рынков, и человек, начав печатать, переставал
   видеть, в каком чате он пишет, и не мог переключиться, не убрав клавиатуру. */
/* Правило-исключение для чата больше не нужно: шапка не прячется нигде. */


/* Список людей: фото, имя, дата появления. Карточка НЕ нажимается - вид
   у неё соответствующий, без рамки и стрелки: нажимаемая на вид, но мёртвая
   карточка раздражает сильнее, чем её отсутствие. */
.person-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: 12px; background: var(--card);
  margin-bottom: 6px;
}
.person-ava {
  position: relative; flex: 0 0 auto; width: 38px; height: 38px;
  border-radius: 50%; overflow: hidden; background: var(--bg-page, #f5f5f7);
  display: flex; align-items: center; justify-content: center;
}
.person-ava img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 2;
}
.person-letter { font-size: 15px; font-weight: 700; color: var(--fg-dim); }
.person-info { flex: 1 1 auto; min-width: 0; }
.person-name {
  font-size: 14px; font-weight: 600; color: var(--fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.person-since { font-size: 12px; color: var(--fg-dim); margin-top: 1px; }

/* Просветы между людьми сжаты (Кирилл). Общий отступ .earn-list в 12px плюс
   свой margin в 6px давали между строками 18px - на карточках с текстом это
   нормально, а здесь строка низкая, и список из десяти человек растягивался
   на два экрана. Список читают глазами сверху вниз, а не по одной карточке:
   плотный он читается быстрее. Отступ задаём в одном месте - у контейнера,
   свой margin у строки снимаем, иначе они складываются. */
/* Два столбца (Кирилл): страница - это ровно десять человек, и в одну колонку
   они уходили под сгиб. В два столбца по пять рядов страница видна целиком,
   и листать её глазами не приходится.

   Строка страниц и сообщения о состоянии тянутся на оба столбца: номер
   страницы, зажатый в половину ширины, читался бы как ещё один человек. */
#peopleList {
  display: grid;
  /* minmax(0, 1fr), а не 1fr: у элемента сетки минимальная ширина по умолчанию
     равна ширине его содержимого, и «1fr» её НЕ ограничивает - это потолок,
     а не пол. Длинное имя в одну строку («Косметика Москва садовод») распирало
     свой столбец шире половины, второй столбец уезжал за край экрана, а
     многоточие не срабатывало: обрезать было нечего, места хватало всегда.
     Ноль в minmax разрешает столбцу быть уже содержимого - только после этого
     text-overflow получает работу. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 4px;
  align-items: start;
}
/* То же самое на два уровня внутрь: строка - элемент сетки, «информация» -
   элемент flex. Обе по умолчанию тоже не ужимаются меньше содержимого, и без
   этих двух нулей ограничение столбца просто передавалось бы дальше вглубь. */
#peopleList .person-row { min-width: 0; }
#peopleList .person-info { min-width: 0; }
/* Дата тоже в одну строку с многоточием: «с 4 сентября 2025» на узкой карточке
   переносилось на второй ряд и делало соседние строки разной высоты. */
#peopleList .person-since {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#peopleList > .pager-row,
#peopleList > .state-msg { grid-column: 1 / -1; }
/* Окантовка (Кирилл): карточка #f4f4f6 на белом фоне отличается от него на
   три процента яркости, и десять таких строк подряд сливаются в одно серое
   поле - границу между двумя людьми глаз не находит. Белый фон плюс тонкая
   рамка разделяют строки надёжнее, чем заливка: рамку видно и на солнце,
   и в тёмной теме, а разницу в три процента - нет.

   Рамка, а не тень: тень под каждой из десяти строк даёт рябь, а на плотном
   списке с отступом в четыре пикселя тени соседей ещё и накладываются. */
#peopleList .person-row {
  margin-bottom: 0; padding: 7px 8px; gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
}
/* В половине ширины имя длиннее полутора слов не помещается - обрезаем
   многоточием, как и раньше, но теперь это работает на каждом втором имени,
   а не на редком длинном. Аватар чуть меньше: он тут опознавательный знак,
   а не портрет. */
#peopleList .person-ava { width: 32px; height: 32px; }
#peopleList .person-name { font-size: 13px; }
#peopleList .person-since { font-size: 11px; }


/* Крупный значок фоном в плитке админки - как на карточках главной.
   Он НЕПОДВИЖЕН, и это принципиально: движущийся слой внутри карточки
   со скруглением заставляет iOS не дорисовывать соседний текст, и счётчик
   показывает «6» вместо «6 платных». Слой снимаем вместе с анимацией -
   will-change без движения ничего не ускоряет, только держит мёртвый слой.
   Размытие делает значок фоном, а не картинкой: он не спорит с цифрой. */
.admin-grid .card-mark {
  display: block;
  position: absolute; right: -6px; bottom: -8px; top: auto;
  font-size: 42px; line-height: 1;
  opacity: .12;
  filter: blur(0.6px);
  animation: none;
  will-change: auto;
  pointer-events: none;
}
.admin-grid .earn-card { position: relative; overflow: hidden; }


/* Строка счётчиков: только значок и число. Слова убраны - с приростом
   в скобках строка не помещалась и переносилась на второй ряд. Разделители
   между счётчиками сохраняем: без них три числа подряд читаются как одно. */
.home-stats-bar { flex-wrap: nowrap; }
.home-stats-item { white-space: nowrap; }

/* Счётчики снова в одну строку, слово рядом с числом. Помещается потому, что
   у людей слова нет: «пользователей» - самое длинное из трёх, и именно оно
   выталкивало строку на второй ряд. Значок человечка говорит то же самое. */


/* Цена и рынок на плитке заявки. Цена - главное после названия: поставщик
   решает по ней, стоит ли вообще открывать. Рынок - вторая половина решения:
   Садовод и Южные Ворота в разных концах города.

   Рынок в одну строку с многоточием: названий может быть три, и перенос
   съедал бы карточку целиком ради строки, которую и так читают до запятой. */
.tile-market {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* «цена не указана» - у заявок старше 22.08, когда цену ещё не спрашивали.
   Тише обычной строки: это отсутствие данных, а не данные. */
.tile-noprice { opacity: .62; font-style: italic; }


/* Заголовок списка людей мельче остальных: это больше не название экрана,
   а его счёт - «579 человек · страница 1 из 58». Строка длиннее обычного
   заголовка, и в прежнем размере она вплотную подходила к кнопке «Назад».
   Отдельной строки со счётом под шапкой больше нет: она повторяла то самое,
   ради чего сюда и заходят, занимая целый ряд. */
#peopleTitle { font-size: 12px; font-weight: 500; }


/* Нижняя строка плитки заявки: слева сколько её смотрели, справа сколько
   обсуждают и сколько уже предложили. Разведены по краям намеренно - это
   величины разной природы: слева чужое внимание, справа ответы на заявку.
   Рядом друг с другом они читались бы как один ряд однородных чисел. */
/* Просмотры - метка на фотографии, слева внизу, напротив предложений справа.
   Тёмная, как метка срока сверху: это счётчик, а не действие; синий цвет
   у предложений значит «тебе ответили», и второй такой же его обесценил бы. */
.tile-badge-views { top: auto; bottom: 7px; left: 7px; right: auto; }


/* Прежний счётчик подмигивал размером: цифра раздувалась и садилась обратно,
   оставаясь на месте. Его заменил барабан (.count-roll выше) - убрано, чтобы
   два разных движения не спорили на одном числе (Кирилл 2026-09-18). */

/* Просмотры в карточке товара - строкой под местом, тем же тоном, что
   категория и рынок: это справка, а не заголовок. */
.product-views { font-size: 12.5px; color: var(--fg-dim); margin-top: 6px; }



/* Прокрутка не перетекает с карточки на страницу под ней: дотянув список
   комментариев до конца, палец продолжает тянуть - и без этого правила
   дальше едет уже лента за карточкой. */
.detail-overlay { overscroll-behavior: contain; }
.detail-card { overscroll-behavior: contain; }


/* Блик на кнопке ответа в карточке заявки. Она ссылка, а не кнопка, поэтому
   свои скругления и обрезку задаём явно: без overflow блик уезжает за угол
   и висит поверх соседних элементов. */
.detail-action.btn-shine { position: relative; overflow: hidden; }


/* Подтверждён ли номер поставщика - рядом с его именем, до всякой оплаты.
   Зелёная метка это довод в его пользу, красная - предупреждение покупателю;
   обе набраны мелко и не спорят с ценой за главное место на карточке. */
.verif-tag {
  display: inline-block; margin-left: 6px; vertical-align: middle;
  font-size: 10.5px; font-weight: 600; line-height: 1.6;
  padding: 1px 7px; border-radius: 999px; white-space: nowrap;
}
.verif-yes { color: #0b6b4f; background: color-mix(in srgb, var(--ok, #0f9d58) 16%, transparent); }
.verif-no  { color: #b02a1e; background: color-mix(in srgb, #dc2626 14%, transparent); }


/* «Открыть контакт» пульсирует: под заявкой бывает десяток предложений, и
   кнопка на фотографии легко теряется среди самих фотографий.

   Пульсирует СВЕЧЕНИЕ, а не размер. Кнопка отцентрована через transform,
   и анимация с трансформом отобрала бы у неё центрирование - тот же самый
   дефект, который мы только что вычищали по всему файлу. Тень ничего
   не двигает. */
@keyframes contact-pulse {
  0%, 100% { box-shadow: 0 2px 8px rgba(0,0,0,.28); }
  50%      { box-shadow: 0 2px 8px rgba(0,0,0,.28), 0 0 0 7px rgba(45,125,246,.30); }
}
.offer-action-btn.offer-action-on-photo {
  animation: contact-pulse 2.2s ease-in-out infinite;
}
/* Открытый контакт мигать не должен - там уже нечего нажимать. */
.offer-action-btn.offer-action-on-photo:disabled { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .offer-action-btn.offer-action-on-photo { animation: none; }
}

/* Имя поставщика вместо безличного номера - крупнее и тёмнее прежней подписи:
   это теперь имя человека, а не служебная нумерация. */
.offer-supplier-label { font-size: 12.5px; }


/* Значок подтверждения у кнопки «Мой профиль». Зелёная галка спокойно
   пульсирует - это награда, её приятно заметить. Красный знак пульсирует
   заметнее: он приглашение к действию, и человек должен на него нажать. */
/* Значок подтверждения вплотную к слову «Профиль»: он относится к нему,
   а не к кнопке. Отодвинутый к краю, он читался как отдельный элемент
   управления - будто на него можно нажать отдельно.

   Без анимации (Кирилл): на главной уже двигаются прирост в счётчиках и
   блики на кнопках; ещё одна пульсирующая точка превращает экран в вывеску. */
.quick-verify {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; flex: 0 0 auto;
  border-radius: 50%; font-size: 10px; font-weight: 700; line-height: 1;
  color: #fff;
  /* Отступ ОТРИЦАТЕЛЬНЫЙ, и это не опечатка. Кнопка профиля - flex-строка
     с gap: 8px, и этот зазор ставится между текстом и значком автоматически,
     сколько бы я ни убавлял свой margin. Минус пять съедает чужой отступ
     и оставляет три пикселя - значок стоит вплотную к слову. */
  margin-left: -5px;
}
.quick-verify-ok { background: var(--ok, #0f9d58); }
.quick-verify-no { background: #dc2626; }


/* Строка «Назад + статус номера». Статус прижат вправо распоркой, как названия
   на остальных экранах: так он стоит у края при любой ширине. */
.profile-topbar { margin-bottom: 10px; }
.profile-topbar .supplier-badge { margin: 0; flex: 0 0 auto; }

/* Карандаш превращается в галочку - это ОДНА кнопка в двух состояниях, поэтому
   и переход рисуем как превращение, а не как подмену: знак поворачивается
   и меняет цвет за те же доли секунды, что человек убирает палец.

   Карандаш отзеркален: значок ✏ смотрит острием вправо-вниз, к полю, а не
   от него. Кнопка стоит справа от имени, и карандаш, направленный наружу,
   читался как «увести отсюда». */
/* Карандаш и галочка - настоящие значки, а не символы шрифта. Символ ✏ на
   разных телефонах рисуется по-своему: где-то цветной эмодзи, где-то мелкая
   загогулина в половину кнопки, и отзеркалить его без последствий нельзя.
   Рисунок ведёт себя одинаково везде и слушается размера.

   Оба значка лежат в кнопке ОДНОВРЕМЕННО и меняются местами: уходящий
   отлетает вниз с поворотом, приходящий влетает сверху и слегка перелетает
   через размер. Подмена содержимого дала бы мгновенный скачок вместо
   превращения. */
.name-save-check { transition: background-color .2s ease, color .2s ease; overflow: hidden; }
.name-save-check.is-save { background: var(--accent); color: var(--accent-fg); }
.name-save-check.saved { background: var(--ok); color: #fff; }

.nsc-ico {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  transition: transform .34s cubic-bezier(.34, 1.56, .64, 1), opacity .2s ease;
}
.nsc-ico svg { width: 17px; height: 17px; display: block; }

/* Карандаш смотрит остриём вправо-вниз, как его и рисуют обычно. Зеркальный
   вариант Кирилл забраковал: перевёрнутый значок читается непривычно, сколько
   бы смысла в направлении ни было. */
.nsc-pencil { transform: none; }
.nsc-check { transform: translateY(-120%) rotate(-90deg); opacity: 0; }

.name-save-check.is-save .nsc-pencil,
.name-save-check.saved .nsc-pencil { transform: translateY(120%) rotate(90deg); opacity: 0; }
.name-save-check.is-save .nsc-check,
.name-save-check.saved .nsc-check { transform: translateY(0) rotate(0); opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .nsc-ico { transition: none; }
}

/* Закрытое поле имени не ловит касание вовсе: на iOS readonly-поле всё равно
   поднимает клавиатуру и ставит курсор, и это читается как разрешение печатать.
   Менять имя можно только карандашом. */
.profile-name-input.locked { pointer-events: none; user-select: none; }

/* Закрытое поле имени не должно выглядеть полем: рамка и фон появляются
   только когда его открыли карандашом. */
.profile-name-input[readonly] {
  border-color: transparent; background: transparent; cursor: default;
}

/* Короткая надпись по центру экрана. Гаснет сама - закрывать нечего. */
.center-flash {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 60; pointer-events: none;
  padding: 12px 20px; border-radius: 14px;
  background: rgba(20, 24, 28, .88); color: #fff;
  font-size: 15px; font-weight: 600; white-space: nowrap;
  box-shadow: 0 8px 28px rgba(0,0,0,.25);
  animation: flash-in .22s ease-out;
}
.center-flash.is-out { opacity: 0; transition: opacity .4s ease; }
@keyframes flash-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(.9); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}


/* Экран ИИ-описаний плотнее: строка сводки теперь одна короткая, а карточки
   не должны отъедать по экрану на запись. Текст описания обрезаем тремя
   строками - его читают, чтобы понять, годное оно или нет, а для этого
   хватает начала. */
#adminGenTotals { margin-bottom: 8px; }
.gen-card { padding: 10px 12px; margin-bottom: 8px; }
/* Свёрнутая карточка: только название и категория. Раскрытая показывает
   описание, отзыв и удаление. Высота едет по фактическому содержимому -
   описания разной длины, и одно число на всех либо режет длинное, либо
   оставляет дыру под коротким. */
.gen-body {
  max-height: 0; overflow: hidden;
  transition: max-height .28s ease, opacity .2s ease;
  opacity: 0;
}
.gen-card.is-open .gen-body { opacity: 1; }

.gen-arrow {
  flex: 0 0 auto; width: 26px; height: 26px; padding: 0;
  border: none; background: none; color: var(--fg-dim);
  font-size: 20px; line-height: 1; cursor: pointer; font-family: inherit;
  transition: transform .28s ease;
}
.gen-card.is-open .gen-arrow { transform: rotate(90deg); }

.gen-head { cursor: pointer; }
.gen-del { margin-top: 10px; }


/* Фильтры в строке с «Назад»: прижаты вправо, где раньше стояло название. */
.gen-tabs { margin: 0; flex: 0 0 auto; gap: 6px; }


/* Две кнопки в раскрытой карточке: возврат и удаление. В строку, а не столбиком -
   они равнозначны, и столбик из двух кнопок читается как список из одной
   важной и одной второстепенной. */
/* Две кнопки в строку. min-width: 0 обязателен: обеим их собственные правила
   задают ширину 100%, и без обнуления минимума они не ужимаются до половины,
   а вылезают за край карточки - именно это и было видно. */
.gen-actions { display: flex; gap: 8px; margin-top: 10px; }
.gen-actions button {
  flex: 1 1 0; min-width: 0; width: auto; margin: 0;
  padding: 11px 8px; font-size: 13.5px; white-space: nowrap;
}
.gen-del { margin-top: 0; }


/* Карточка товара повторяет карточку заявки. Описание тем же тоном, что
   и остальная справка, но с сохранением переносов строк: поставщики пишут
   его в несколько строк, и склеенное в абзац оно теряет смысл. */
#productBody .detail-title { font-size: 17px; font-weight: 600; letter-spacing: -0.2px; margin: 0 0 12px; }
#productBody .product-desc { white-space: pre-line; margin-bottom: 11px; }
#productBody .detail-status { color: var(--fg); font-weight: 600; }


/* Значок «поделиться» в шапке статистики рекламы. Заголовок здесь не h2,
   а строка в панели, поэтому выравнивание задаём ей отдельно - иначе значок
   встаёт по базовой линии текста и висит ниже, чем нужно. */
.pitch-topbar-title[data-share] { display: inline-flex; align-items: center; gap: 6px; line-height: 1; }
.pitch-topbar-title .share-btn.copy-btn {
  width: 22px; height: 22px; flex: 0 0 auto; padding: 0;
  border: none; background: none; box-shadow: none; border-radius: 0;
  opacity: .38;
  /* Значок опущен на два пикселя: он квадратный, а буквы рядом с ним имеют
     выносные элементы вверх и не имеют вниз - по геометрии значок кажется
     приподнятым, хотя по центрированию стоит ровно. */
  display: flex; align-items: center; justify-content: center;
  margin-top: 2px;
}
.pitch-topbar-title .share-btn.copy-btn:active { opacity: .7; }
.pitch-topbar-title .share-btn.copy-btn.is-done { opacity: 1; background: none; }
.pitch-topbar-title .share-btn .copy-ico { width: 14px; height: 14px; }


/* Переключатель разреза на графике рекламы. Стоит на месте подписи «Последние
   24 часа»: подпись сообщала период, кнопки сообщают его же и позволяют
   сменить - одно место вместо двух. */
/* Четыре кнопки в строку с отметкой «онлайн» - места в обрез, поэтому поля
   узкие и шрифт мелкий. Переносить их на второй ряд нельзя: строка над
   графиком должна оставаться одной. */
.chart-range { display: inline-flex; gap: 4px; flex-wrap: nowrap; }
.chart-range-btn {
  padding: 4px 8px; border-radius: 999px; white-space: nowrap;
  border: 1px solid var(--border); background: transparent; color: var(--fg-dim);
  font-family: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease;
}
.chart-range-btn.is-on {
  background: var(--pine, #0f9d58); border-color: var(--pine, #0f9d58); color: #fff;
}


/* Профиль поставщика оформлен как карточка заявки: блоки разделены чертой,
   а не сложены плитками с полями. Два экрана с контактами продавца выглядели
   по-разному, и человек каждый раз заново искал, где что. */
.sp-card {
  background: transparent; border: none; border-radius: 0;
  padding: 12px 0; margin: 0;
  border-top: 1px solid var(--border);
}
.sp-head { padding-bottom: 12px; }
.sp-row { display: flex; justify-content: space-between; gap: 12px; padding: 3px 0; }
.sp-label { color: var(--fg-dim); font-size: 13px; }
.sp-value { font-size: 13.5px; font-weight: 600; text-align: right; }


/* ── Экран «Заявки» в админке ───────────────────────────────────────────── */
/* Карточка заявки: фото, название, условия, под ними воронка счётчиков.
   Порядок счётчиков слева направо повторяет путь заявки - смотрели, дошло,
   ответили, купили контакт: по нему видно, на каком шаге всё встало. */
.rfq-admin-card { text-align: left; width: 100%; }
.rfq-admin-head { display: flex; align-items: flex-start; gap: 10px; }
.rfq-admin-photo {
  width: 44px; height: 44px; flex: 0 0 auto; border-radius: 10px;
  object-fit: cover; background: var(--card);
}
.rfq-admin-text { flex: 1 1 auto; min-width: 0; }
.rfq-live {
  flex: 0 0 auto; font-size: 10.5px; font-weight: 700; padding: 1px 7px;
  border-radius: 999px; color: #0b6b4f;
  background: color-mix(in srgb, var(--ok, #0f9d58) 16%, transparent);
}
.rfq-counts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
.rfq-count { font-size: 12px; color: var(--fg-dim); white-space: nowrap; }

/* Получатели и покупатели - строками «кто : что с ним стало». Список из
   двадцати человек читают глазами сверху вниз, и статус должен стоять
   у правого края, чтобы взгляд шёл по одной линии. */
.rfq-people { display: flex; flex-direction: column; gap: 2px; }
.rfq-person {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 12.5px; padding: 4px 0; border-bottom: 1px solid var(--border);
}
.rfq-person:last-child { border-bottom: none; }
.rfq-status { color: var(--fg-dim); white-space: nowrap; }
.rfq-status.is-ok { color: var(--ok, #0f9d58); font-weight: 600; }
.rfq-status.is-bad { color: #b02a1e; }

.rfq-offer { display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.rfq-offer:last-child { border-bottom: none; }
.rfq-offer-photo { width: 56px; height: 56px; flex: 0 0 auto; border-radius: 10px; object-fit: cover; }
.rfq-offer-text { flex: 1 1 auto; min-width: 0; font-size: 13px; line-height: 1.45; }


/* ── Своя рассылка ───────────────────────────────────────────────────────── */
/* Фотографии плиткой, как в Telegram: человек собирает альбом и должен видеть
   его тем же способом, каким увидит получатель. */
/* Четыре в ряд: десять фотографий укладываются в три ряда вместо пяти, и
   экран перестаёт быть лентой из картинок. Сетка, а не свободная строка -
   плитки должны стоять по столбцам, иначе при перестановке они прыгают
   по-разному в зависимости от ширины соседей. */
.mailing-photos {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 6px; margin: 10px 0;
}
.mailing-photo {
  position: relative; width: 100%; aspect-ratio: 1 / 1;
  animation: mailing-photo-in .26s cubic-bezier(.34, 1.4, .64, 1);
}
@keyframes mailing-photo-in {
  from { opacity: 0; transform: scale(.82); }
  to   { opacity: 1; transform: scale(1); }
}
.mailing-photo img { width: 100%; height: 100%; object-fit: cover; border-radius: 10px; display: block; }
.mailing-photo-x {
  position: absolute; top: -4px; right: -4px; width: 18px; height: 18px;
  border: none; border-radius: 50%; background: rgba(0,0,0,.65); color: #fff;
  font-size: 11px; line-height: 1; cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
}

/* Полоса отправки. Заполняется по факту доставленных, а не по времени:
   полоса, которая едет сама по себе, врёт - а здесь врать нельзя, за ней
   стоят живые люди, получающие сообщение. */
.mailing-progress { margin: 12px 0; }
.mailing-bar {
  height: 8px; border-radius: 999px; background: var(--card); overflow: hidden;
}
.mailing-bar-fill {
  height: 100%; width: 0%; border-radius: 999px;
  background: var(--pine, #0f9d58);
  transition: width .6s ease;
}
.mailing-progress-text {
  margin-top: 6px; font-size: 13px; font-weight: 600; color: var(--fg); text-align: center;
}


/* Номер на плитке: порядок в альбоме видит получатель, значит и отправитель
   должен видеть его здесь, а не проверять на себе. */
.mailing-photo-n {
  position: absolute; left: 3px; bottom: 3px;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: rgba(0,0,0,.62); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
}
/* Перетаскиваемая плитка приподнимается: без обратной связи непонятно,
   взялась она или палец просто скользит по экрану. */
/* Взятая плитка приподнимается и отбрасывает тень - без этого непонятно,
   взялась она или палец просто скользит по экрану. Возврат помягче взятия:
   резкое схлопывание читается как сбой. */
.mailing-photo.is-dragging {
  transform: scale(1.1) rotate(-2deg); opacity: .9; z-index: 2;
  box-shadow: 0 8px 20px rgba(20, 24, 28, .28);
  animation: none;
}
/* Перемещение плавнее взятия: плитка едет на новое место почти треть секунды,
   и глаз успевает проследить путь. Резкая перестановка читается как подмена -
   человек не понимает, его ли это фотография переехала. */
.mailing-photo {
  transition: transform .32s cubic-bezier(.22, .61, .36, 1),
              opacity .18s ease, box-shadow .22s ease;
  touch-action: none;
}
.mailing-photo img { transition: border-radius .2s ease; }

@media (prefers-reduced-motion: reduce) {
  .mailing-photo { animation: none; transition: none; }
}



/* Кнопка отправки с числом: текст длиннее обычного, поэтому не переносим
   и слегка ужимаем на узких экранах, вместо того чтобы ломать её на две
   строки посреди слова. */
#adminMailingSend { white-space: nowrap; }


/* Перебор по длине: красным и с коротким подрагиванием. Цвет говорит, что
   что-то не так, движение - что это случилось именно сейчас, от последнего
   набранного символа. */
.mailing-len-over { animation: len-shake .3s ease-out; font-weight: 600; }
@keyframes len-shake {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-3px); }
  75%      { transform: translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .mailing-len-over { animation: none; }
}


/* Счётчик внутри поля, в правом нижнем углу. Сквозной для касаний: иначе
   он перехватывал бы палец у самого края текста. Поле получает отступ снизу,
   чтобы последняя строка не забегала под число. */
.mailing-text-wrap { position: relative; }
.mailing-text-wrap .feedback-textarea { padding-bottom: 26px; }
.mailing-len {
  position: absolute; right: 12px; bottom: 10px;
  font-size: 11.5px; color: var(--fg-dim); pointer-events: none;
  background: transparent;
}
.mailing-len.error { color: #b02a1e; }

/* Строка действий: значок камеры слева, отправка справа. */
.mailing-actions { display: flex; align-items: stretch; gap: 8px; margin-top: 8px; }
.mailing-actions .submit-btn { flex: 1 1 auto; margin: 0; }
.mailing-photo-btn {
  flex: 0 0 auto; width: 52px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--card);
  font-size: 20px; line-height: 1; cursor: pointer; font-family: inherit;
}
.mailing-photo-btn:disabled { opacity: .4; }


/* Стоп рядом с отправкой, той же высоты. Красный и квадратный: его ищут
   глазами в момент, когда всё идёт не так, и он не должен быть похож
   на соседнюю кнопку. */
.mailing-stop-btn {
  flex: 0 0 auto; width: 52px; border-radius: 12px;
  border: 1px solid color-mix(in srgb, #dc2626 40%, transparent);
  background: color-mix(in srgb, #dc2626 12%, transparent);
  color: #b02a1e; font-size: 18px; line-height: 1;
  cursor: pointer; font-family: inherit;
}
.mailing-stop-btn:active { opacity: .75; }


/* Разосланное сообщение: кнопка гаснет и перестаёт нажиматься. Серый вместо
   зелёного - чтобы палец, летящий по привычке в ту же точку, увидел разницу
   раньше, чем нажмёт. */
#adminMailingSend.is-done {
  background: var(--card); color: var(--fg-dim);
  box-shadow: none; cursor: default;
}
#adminMailingSend.is-done::after { display: none; }


/* Черта перед архивом: он не часть отправки, а взгляд назад. */
.mailing-divider { margin: 16px 0 12px; }

/* Фотографии в архиве мельче - их разглядывают, только чтобы вспомнить,
   какая это была рассылка, а не чтобы выбирать порядок. */
.arch-photos { grid-template-columns: repeat(5, 1fr); gap: 4px; }
.arch-photos .mailing-photo { animation: none; }


/* Пост в архиве - в рамке сообщения, как его видел получатель: фотографии
   сверху, текст под ними. Список цифр без самого поста не даёт вспомнить,
   что именно рассылали. */
.arch-post {
  margin-top: 10px; padding: 10px 12px;
  border-radius: 12px; background: var(--card);
}
.arch-text {
  font-size: 13px; line-height: 1.5; color: var(--fg);
  white-space: pre-line; margin-top: 8px;
}
.arch-post .arch-photos { margin: 0; }
.arch-post .arch-photos:only-child { margin: 0; }


/* ── Заказ рекламы в чатах ───────────────────────────────────────────────── */
/* Календарь плиткой по четыре: две недели укладываются в четыре ряда, и весь
   выбор виден без прокрутки - а прокрутка на выборе дат сбивает: человек
   не видит, что было слева. */
/* Календарь рекламы в чатах пользуется теми же плитками, что бронь баннера
   (.schedule-*): один и тот же выбор суток не должен выглядеть по-разному
   на двух экранах. Свой отступ - единственное, что здесь добавлено. */
#adChatDays { margin: 8px 0; }

.ad-price {
  margin: 12px 0 4px; font-size: 15px; text-align: center; color: var(--fg-dim);
}
.ad-price b { font-size: 19px; color: var(--fg); }

/* Видео в плитках показываем значком: кадр из файла браузер отдаёт не всегда,
   а пустой квадрат читается как незагрузившееся фото. */
/* Ролик в ленте выглядит как фотография: тот же размер, та же обрезка, тот же
   радиус. Лента из фото и видео должна читаться одной лентой, а не двумя
   разными вещами рядом. Нажатия не ловим - иначе iOS раскрывает видео на весь
   экран вместо того, чтобы дать перетащить плитку. */
.ad-video-tile {
  width: 100%; height: 100%; border-radius: 10px; background: var(--card);
  object-fit: cover; display: block; pointer-events: none;
}
/* Заглушка - когда файла ещё нет или он слишком тяжёлый для обратной отдачи. */
.ad-video-stub {
  display: flex; align-items: center; justify-content: center; font-size: 26px;
}
/* Длительность в углу: у играющего ролика нет ни одной подсказки, сколько он
   идёт, а для рекламного поста это первое, что нужно знать. */
.ad-video-dur {
  position: absolute; right: 3px; bottom: 3px;
  height: 16px; padding: 0 5px; border-radius: 999px;
  background: rgba(0, 0, 0, .62); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px;
}
/* Строка хода работы вместо подсказки: сжатие идёт столько же, сколько сам
   ролик, и без цветного отличия человек читает её как обычный текст и уходит. */
.hint.is-work { color: var(--accent); font-weight: 600; }


/* Вкладки «Моей рекламы» - той же формы, что разрезы графика. */
#adsMineTabs { margin: 0 0 10px; }


/* ── Карточка одобренного поставщика ─────────────────────────────────────── */
/* Была выписка «поле: значение» построчно - так выглядит база данных, а не
   человек, которого выбирают. Стало: имя и подтверждение сверху, место и
   телефоны строками со значками, категории плитками. */
.sup-card {
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 16px; padding: 14px; margin: 10px 0;
}
.sup-head { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.sup-ava {
  width: 46px; height: 46px; flex: 0 0 auto; border-radius: 50%;
  background: var(--card); color: var(--fg-dim);
  display: flex; align-items: center; justify-content: center;
  font-size: 19px; font-weight: 700;
}
.sup-head-text { min-width: 0; }
.sup-name { font-size: 16px; font-weight: 600; letter-spacing: -0.2px; }
.sup-sub { font-size: 12.5px; color: var(--ok, #0f9d58); font-weight: 600; margin-top: 2px; }

.sup-row {
  display: flex; gap: 8px; align-items: baseline;
  font-size: 13.5px; padding: 4px 0; border-top: 1px solid var(--border);
}
.sup-ico { flex: 0 0 auto; }

/* Категории плитками: их выхватывают глазами по одной, а строка через запятую
   читается целиком, даже когда нужна одна. */
.sup-cats { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.sup-cat {
  font-size: 11.5px; line-height: 1.7; padding: 1px 9px;
  border-radius: 999px; background: var(--card); color: var(--fg-dim);
}


/* Плитка «+» в конце ленты медиа: добавление стоит там же, где перестановка,
   и лента читается как один набор, а не как список плюс кнопка внизу. */
.ad-add-tile {
  border: 1px dashed var(--border); background: var(--card); color: var(--fg-dim);
  border-radius: 10px; font-size: 24px; line-height: 1; cursor: pointer;
  font-family: inherit; padding: 0;
  display: flex; align-items: center; justify-content: center;
  animation: none;
}
.ad-add-tile:active { opacity: .7; }


/* Плитки объявления ведут себя как в рассылке: взятая приподнимается, слегка
   наклоняется и отбрасывает тень, соседи разъезжаются плавно. Отдельное
   правило нужно, потому что общий стиль .mailing-photo действует и здесь,
   а вот скругление и переход у картинки внутри - свои. */
#adChatPhotos .mailing-photo { touch-action: none; }
#adChatPhotos .mailing-photo.is-dragging {
  transform: scale(1.1) rotate(-2deg); opacity: .9; z-index: 3;
  box-shadow: 0 8px 20px rgba(20, 24, 28, .28);
}
#adChatPhotos img { border-radius: 10px; }


/* ── Просмотр прикреплённого медиа ────────────────────────────────────────
   Тёмная подложка и кнопки поверх файла. Курсор не «zoom-out», как у обычного
   увеличения фото: отсюда листают, а не только закрывают. */
.photo-zoom.mv { cursor: default; }

.mv-stage {
  max-width: 100%; max-height: 100%;
  display: flex; align-items: center; justify-content: center;
  padding: 0 52px;   /* место под стрелки: иначе они лежат на самом снимке */
}
.mv-stage img, .mv-stage video {
  max-width: 100%; max-height: 76vh;
  border-radius: 14px; display: block; background: #000;
}

/* Счётчик вверху по центру: на десяти файлах без него не понять, где ты. */
.mv-count {
  position: absolute; top: 16px; left: 0; right: 0; text-align: center;
  color: rgba(255, 255, 255, .74); font-size: 13px; font-weight: 700;
  letter-spacing: .02em; pointer-events: none;
}

.mv-close {
  position: absolute; top: 10px; right: 12px; z-index: 2;
  width: 34px; height: 34px; padding: 0; border: none; border-radius: 50%;
  background: rgba(255, 255, 255, .14); color: #fff; font-size: 15px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; font-family: inherit;
}

/* Стрелки прижаты к краям и высокие: на ходу в них попадают не глядя. */
.mv-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 42px; height: 60px; padding: 0; border: none; border-radius: 12px;
  background: rgba(255, 255, 255, .12); color: #fff;
  font-size: 30px; line-height: 1; font-family: inherit;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.mv-prev { left: 6px; }
.mv-next { right: 6px; }
.mv-arrow:active, .mv-close:active { background: rgba(255, 255, 255, .26); }
.mv-arrow.hidden { display: none; }

/* На узком экране стрелки съедают снимок - уводим их вниз, к краям. */
@media (max-width: 420px) {
  .mv-stage { padding: 0 8px; }
  .mv-arrow { top: auto; bottom: 18px; transform: none; width: 56px; height: 46px; }
}


/* Поднятая плитка объявления. Тень и лёгкая полупрозрачность говорят, что
   она «в руке»; сам сдвиг и увеличение задаёт код - плитка идёт за пальцем,
   и правило в стилях перебивало бы её положение на каждом кадре. */
.mailing-photo.is-lifted {
  z-index: 3;
  opacity: .94;
  animation: none;
  box-shadow: 0 12px 26px rgba(20, 24, 28, .34);
  will-change: transform;
}
.mailing-photo.is-lifted .mailing-photo-x { opacity: 0; }

/* Соседи расступаются заметно, но не медленно: на перестановке в четыре
   колонки путь короткий, и долгая дорога читается как подтормаживание. */
#adChatPhotos .mailing-photo {
  transition: transform .24s cubic-bezier(.22, .61, .36, 1),
              opacity .18s ease, box-shadow .2s ease;
}

@media (prefers-reduced-motion: reduce) {
  #adChatPhotos .mailing-photo { transition: none; }
}


/* Название площадки, которое ведёт в канал.
   Подчёркивание рисуем фоном, а не text-decoration: у фона можно анимировать
   ширину, у подчёркивания - нельзя. Слоя два и лежат они друг на друге:
   пунктир в состоянии покоя и сплошная линия, которая при наведении
   вырастает слева направо, как будто ссылку проводят пальцем. Пунктир
   остаётся на месте - линия не появляется из пустоты, а закрашивает то,
   что уже было.
   Сплошным подчёркиванием в покое здесь нельзя: строка длинная и жирная,
   и линия под ней читается как зачёркивание. */
.place-name.is-link {
  color: var(--link);
  cursor: pointer;
  text-decoration: none;
  padding-bottom: 3px;
  background-repeat: no-repeat;
  background-image:
    repeating-linear-gradient(to right, currentColor 0 2px, transparent 2px 5px),
    linear-gradient(to right, currentColor, currentColor);
  background-position: 0 100%, 0 100%;
  background-size: 100% 1px, 0 2px;
  transition: background-size .3s cubic-bezier(.22, .61, .36, 1),
              filter .18s ease;
}

/* Только там, где указатель действительно есть. На телефоне :hover
   «залипает» после нажатия, и ссылка осталась бы подчёркнутой навсегда. */
@media (hover: hover) {
  .place-name.is-link:hover {
    background-size: 100% 1px, 100% 2px;
    filter: brightness(1.15);
  }
}

/* Отклик на палец. Наведения на телефоне не бывает, поэтому здесь работает
   не :hover, а короткая анимация, которую ставит код по нажатию: линия
   пробегает слева направо, имя на миг светлеет - и гаснет обратно.
   Через :active это делать нельзя: в iOS он на обычных элементах срабатывает
   через раз, и половина нажатий оставалась бы без отклика. */
.place-name.is-hit {
  animation: place-link-hit .42s cubic-bezier(.22, .61, .36, 1);
}

@keyframes place-link-hit {
  0%   { background-size: 100% 1px, 0 2px;    filter: none; }
  45%  { background-size: 100% 1px, 100% 2px; filter: brightness(1.3); }
  100% { background-size: 100% 1px, 0 2px;    filter: none; }
}

@media (prefers-reduced-motion: reduce) {
  .place-name.is-hit { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .place-name.is-link { transition: none; }
}


/* Шапка витрины для тех, кто зашёл по прямой ссылке из браузера. Стоит НАД плитками,
   а не вместо них: человек должен сразу видеть, что внутри, и только потом - вход. */
.browser-note {
  margin: 0 0 14px;
  padding: 16px;
  border-radius: 14px;
  background: var(--card);
  text-align: center;
}
.browser-note-title { font-size: 16px; font-weight: 800; margin-bottom: 6px; }
.browser-note-text {
  font-size: 13px; line-height: 1.45; color: var(--muted, #6b7280); margin-bottom: 12px;
}
.browser-note-btn { width: 100%; }


/* Окно «Вы не авторизованы». Поверх экрана, по центру - человек уже листал разделы,
   и сообщение должно остановить его ровно в тот момент, когда понадобился вход. */
.auth-gate {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(17, 20, 24, .45);
}
.auth-gate-box {
  width: 100%; max-width: 340px;
  background: #fff; border-radius: 18px; padding: 22px 18px;
  text-align: center;
  box-shadow: 0 18px 44px rgba(20, 24, 28, .28);
}
.auth-gate-title { font-size: 18px; font-weight: 800; margin-bottom: 8px; }
.auth-gate-text {
  font-size: 13px; line-height: 1.45; color: var(--muted, #6b7280); margin-bottom: 16px;
}
.auth-gate-btn { width: 100%; }
/* Выход из окна обязателен: без него осмотр витрины превращается в тупик. */
.auth-gate-close {
  width: 100%; margin-top: 10px; padding: 10px;
  border: none; background: none; cursor: pointer;
  font-family: inherit; font-size: 13px; color: var(--muted, #6b7280);
}


/* ═══ Движения, которых нет в готовых наборах ══════════════════════════════
   Баннер живёт на чужом экране три секунды. Узнаваемое «появление» глаз
   пропускает не глядя - он видел его тысячу раз. Задерживает незнакомое.
   Все длительности кратны --ad-t, как и у прежних: ползунок скорости
   двигает их заодно, отдельной настройки не появляется. */

/* СБОРКА. Буквы слетаются из разных сторон, каждая со своим поворотом.
   Разброс задаёт код (--dx/--dy/--dr): в стилях случайности нет, а без неё
   получился бы обычный сдвиг, а не сборка. */
.ad-tanim-assemble .ad-banner-title span {
  display: inline-block; white-space: pre;
  animation: ad-assemble calc(var(--ad-t, 1s) * .85) cubic-bezier(.18, .9, .25, 1) both;
  animation-delay: calc(var(--i, 0) * var(--ad-t, 1s) * .022);
}
@keyframes ad-assemble {
  from { opacity: 0; transform: translate(var(--dx, 0), var(--dy, 0)) rotate(var(--dr, 0)) scale(.6); }
  to   { opacity: 1; transform: none; }
}
.ad-tanim-assemble .ad-banner-sub {
  animation: ad-fade-in calc(var(--ad-t, 1s) * .8) ease-out calc(var(--ad-t, 1s) * .55) both;
}

/* ПЕЧАТЬ. Буквы возникают по одной, за ними идёт курсор. Ступенчато, без
   плавности: печатная машинка не «проявляет» знак, она его ставит. */
.ad-tanim-type .ad-banner-title span {
  display: inline-block; white-space: pre;
  animation: ad-type-step calc(var(--ad-t, 1s) * .01) steps(1, end) both;
  animation-delay: calc(var(--i, 0) * var(--ad-t, 1s) * .055);
}
@keyframes ad-type-step { from { opacity: 0; } to { opacity: 1; } }
.ad-tanim-type .ad-banner-title::after {
  content: "";
  display: inline-block;
  width: .08em; height: .95em;
  margin-left: .04em;
  vertical-align: -.1em;
  background: currentColor;
  animation: ad-caret 1s steps(1, end) infinite;
}
@keyframes ad-caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
.ad-tanim-type .ad-banner-sub {
  animation: ad-fade-in calc(var(--ad-t, 1s) * .7) ease-out calc(var(--ad-t, 1s) * .9) both;
}

/* ПОМЕХИ. Изображение будто срывается: цветовые каналы расходятся, строки
   дёргаются. Держится недолго и уходит - постоянные помехи мешали бы читать,
   а прочесть человек должен успеть. */
.ad-tanim-glitch .ad-banner-title {
  animation: ad-glitch calc(var(--ad-t, 1s) * 1.1) steps(1, end) both;
}
@keyframes ad-glitch {
  0%   { opacity: 0; transform: translateX(-6px) skewX(9deg);
         text-shadow: 3px 0 rgba(255,0,80,.9), -3px 0 rgba(0,230,255,.9); }
  12%  { opacity: 1; transform: translateX(5px) skewX(-7deg);
         text-shadow: -4px 0 rgba(255,0,80,.9), 4px 0 rgba(0,230,255,.9); }
  24%  { transform: translateX(-3px) skewX(4deg);
         text-shadow: 2px 0 rgba(255,0,80,.7), -2px 0 rgba(0,230,255,.7); }
  36%  { transform: translateX(2px);
         text-shadow: -2px 0 rgba(255,0,80,.5), 2px 0 rgba(0,230,255,.5); }
  50%  { transform: none; text-shadow: 1px 0 rgba(255,0,80,.3), -1px 0 rgba(0,230,255,.3); }
  100% { transform: none; text-shadow: var(--ad-shadow, none); }
}
.ad-tanim-glitch .ad-banner-sub {
  animation: ad-fade-in calc(var(--ad-t, 1s) * .6) ease-out calc(var(--ad-t, 1s) * .8) both;
}

/* НЕОН. Вывеска, которая разгорается не сразу: подмигивает, гаснет, и только
   потом встаёт ровным светом. Неровность и есть весь смысл - ровное свечение
   у нас уже было. */
.ad-tanim-neon .ad-banner-title {
  animation: ad-neon calc(var(--ad-t, 1s) * 1.8) ease-out both;
}
@keyframes ad-neon {
  0%   { opacity: .15; text-shadow: none; }
  7%   { opacity: 1;   text-shadow: 0 0 10px rgba(120,220,255,.95), 0 0 22px rgba(60,170,255,.7); }
  11%  { opacity: .2;  text-shadow: none; }
  18%  { opacity: 1;   text-shadow: 0 0 12px rgba(120,220,255,.95), 0 0 26px rgba(60,170,255,.7); }
  23%  { opacity: .3;  text-shadow: none; }
  31%  { opacity: 1;   text-shadow: 0 0 9px rgba(120,220,255,.9), 0 0 20px rgba(60,170,255,.6); }
  36%  { opacity: .5;  text-shadow: 0 0 4px rgba(120,220,255,.5); }
  100% { opacity: 1;   text-shadow: 0 0 8px rgba(120,220,255,.75), 0 0 18px rgba(60,170,255,.45); }
}
.ad-tanim-neon .ad-banner-sub {
  animation: ad-fade-in calc(var(--ad-t, 1s) * .7) ease-out calc(var(--ad-t, 1s) * 1.1) both;
}

/* ШТОРКА. Полоса проходит по строке и открывает текст за собой. Приём из
   печатной рекламы: сначала движение, потом слово. */
.ad-tanim-wipe .ad-banner-title {
  position: relative;
  clip-path: inset(0 100% 0 0);
  animation: ad-wipe-open calc(var(--ad-t, 1s) * .75) cubic-bezier(.6, 0, .2, 1) both;
}
@keyframes ad-wipe-open { to { clip-path: inset(0 0 0 0); } }
.ad-tanim-wipe .ad-banner-title::before {
  content: "";
  position: absolute; top: -.08em; bottom: -.08em; left: 0; width: .5em;
  background: var(--accent, #2e7cf6);
  animation: ad-wipe-bar calc(var(--ad-t, 1s) * .75) cubic-bezier(.6, 0, .2, 1) both;
}
@keyframes ad-wipe-bar {
  0%   { transform: translateX(0); opacity: 1; }
  92%  { transform: translateX(calc(100% + 100cqw)); opacity: 1; }
  100% { transform: translateX(calc(100% + 100cqw)); opacity: 0; }
}
.ad-tanim-wipe .ad-banner-sub {
  animation: ad-fade-in calc(var(--ad-t, 1s) * .7) ease-out calc(var(--ad-t, 1s) * .6) both;
}

/* ФОН: СКАНИРОВАНИЕ. Светлая линия идёт сверху вниз - снимок будто считывают.
   В отличие от «Блика» линия узкая и движется в одну сторону, без возврата. */
.ad-banim-scan { position: relative; overflow: hidden; }
.ad-banim-scan::before {
  content: "";
  position: absolute; left: 0; right: 0; height: 18%;
  z-index: 1;
  background: linear-gradient(to bottom, transparent,
              rgba(150, 230, 255, .32), rgba(255, 255, 255, .18), transparent);
  animation: ad-scan-move calc(var(--ad-t, 1s) * 7) linear infinite;
  pointer-events: none;
}
@keyframes ad-scan-move {
  from { transform: translateY(-120%); }
  to   { transform: translateY(660%); }
}

/* ФОН: ЗЕРНО. Плёночное подрагивание - снимок оживает, оставаясь неподвижным.
   Единственное движение фона, которое не сдвигает картинку: полезно там, где
   на снимке важен край и любой сдвиг его срезает. */
.ad-banim-grain .ad-banner-bg {
  animation: ad-grain calc(var(--ad-t, 1s) * .9) steps(3, end) infinite;
}
@keyframes ad-grain {
  0%   { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1)     contrast(1); }
  33%  { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1.045) contrast(1.03); }
  66%  { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(.975)  contrast(.99); }
  100% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1)     contrast(1); }
}

/* Кому тяжело от движения - показываем без него. Правило обязано стоять ПОСЛЕ
   всех новых движений, иначе оно их не перекроет. */
@media (prefers-reduced-motion: reduce) {
  .ad-tanim-assemble .ad-banner-title span,
  .ad-tanim-type .ad-banner-title span,
  .ad-tanim-glitch .ad-banner-title,
  .ad-tanim-neon .ad-banner-title,
  .ad-tanim-wipe .ad-banner-title,
  .ad-banim-scan::before,
  .ad-banim-grain .ad-banner-bg { animation: none; }
  .ad-tanim-wipe .ad-banner-title { clip-path: none; }
  .ad-tanim-wipe .ad-banner-title::before,
  .ad-tanim-type .ad-banner-title::after { display: none; }
}


/* ═══ Переписка с поставщиком ═════════════════════════════════════════════ */

/* Шапка: кто перед тобой и в каком он состоянии. Всё в одной строке -
   на телефоне вертикаль дороже горизонтали, а имя с состоянием читаются
   как одно целое, а не как два разных сообщения. */
.dlg-head {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0 10px;
}
.dlg-peer {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 10px;
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: inherit; text-align: left;
}
.dlg-ava {
  flex: 0 0 auto;
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--card) center/cover no-repeat;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 800; color: var(--fg-dim);
}
.dlg-who { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dlg-name {
  font-size: 15px; font-weight: 800; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Состояние и оценка одной строкой, помельче: это справка, а не заголовок. */
.dlg-sub {
  font-size: 12px; color: var(--fg-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dlg-sub .is-online { color: var(--pine, #0f9d58); font-weight: 700; }

.dlg-contacts {
  flex: 0 0 auto;
  padding: 7px 12px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  font-family: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
}
.dlg-contacts:active { background: var(--card); }

/* Предупреждение. Не красное и не пугающее: это не ошибка, а условие, на
   котором работает площадка. Красным человек читает как сбой и закрывает. */
.dlg-warn {
  margin: 8px 0 4px;
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--card);
  font-size: 12px; line-height: 1.45; color: var(--fg-dim);
}

/* Список переписок */
.dlg-row {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 12px; margin-bottom: 8px;
  border: none; border-radius: 14px; background: var(--card);
  font-family: inherit; text-align: left; cursor: pointer;
}
.dlg-row-body { flex: 1 1 auto; min-width: 0; }
.dlg-row-top { display: flex; align-items: baseline; gap: 8px; }
.dlg-row-name {
  flex: 1 1 auto; min-width: 0;
  font-size: 15px; font-weight: 800; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dlg-row-at { flex: 0 0 auto; font-size: 11px; color: var(--fg-dim); }
.dlg-row-last {
  font-size: 13px; color: var(--fg-dim); margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Счётчик непрочитанного - единственное здесь цветное пятно: за ним и
   приходят на этот экран. */
.dlg-row-unread {
  flex: 0 0 auto;
  min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: 999px; background: var(--accent); color: #fff;
  font-size: 11px; font-weight: 800; line-height: 20px; text-align: center;
}


/* ═══ Одинаковый вид баннера на всех телефонах ═════════════════════════════
   Заголовок держим В ОДНУ строку: перенос ломает композицию - подпись уезжает
   вниз, снимок обрезается по-другому, и один и тот же баннер выглядит на
   Android иначе, чем на iPhone. Размер под ширину подгоняет код (fitAdTitle),
   здесь только запрет переноса и защита от того, чтобы слишком длинное слово
   вылезло за край. */
.ad-banner-photo .ad-banner-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
}

/* Android умеет увеличивать шрифты системной настройкой, и она масштабирует
   текст ПОВЕРХ заданного размера - баннер разъезжается у тех, кто включил
   крупный шрифт. Баннер - изображение с точной композицией, а не текст статьи:
   его размеры задаёт заказчик, и системный масштаб их ломает. */
.ad-banner, .ad-banner * {
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
}


/* Вход в поставщики над списком. Занимает всю ширину сетки, а не половину
   клетки: это не ещё один поставщик в ряду, а другое по смыслу действие -
   и выглядеть должно иначе, иначе глаз пролистает его как карточку. */
.sup-cta {
  grid-column: 1 / -1;
  display: flex; flex-direction: column; gap: 3px;
  width: 100%; padding: 14px 16px; margin-bottom: 10px;
  border: none; border-radius: 14px;
  background: var(--accent); color: #fff;
  font-family: inherit; text-align: left; cursor: pointer;
}
.sup-cta:active { transform: scale(.995); }
.sup-cta-title { font-size: 15px; font-weight: 800; }
.sup-cta-text { font-size: 12.5px; line-height: 1.4; opacity: .92; }
/* Своя карточка тем же зелёным (Кирилл): приглушённую видно хуже, а вход в
   собственную карточку нужен поставщику чаще, чем новичку - приглашение. */

/* Кому тяжело от движения - подмена кнопок без поворота и поле без проявления:
   смысл передают цвет и сам знак, движение тут было усилением, а не носителем
   смысла. Отражение карандаша оставляем: это не движение, а его вид. */
@media (prefers-reduced-motion: reduce) {
  .chat-admin-edit, .chat-admin-delete { transition: none; }
  .chat-edit-input { animation: none; }
}

/* Кнопка «Удалить навсегда» в архиве. Красная, но не кричащая: действие
   необратимое, однако рядом стоит обычный «Вернуть», и два одинаково ярких
   пятна заставляли бы выбирать между ними дольше, чем нужно. */
.camp-wide-btn.is-danger { color: #dc2626; }
.camp-wide-btn.is-danger:active { background: #fef2f2; }

/* ═══ Переключение рынка в чате ════════════════════════════════════════════ */

/* Отклик самой вкладки: короткое сжатие под палец. Нажатие должно ощущаться
   сразу, ещё до того как приедут сообщения - иначе на медленной сети кажется,
   что тап не засчитался, и человек жмёт второй раз. */
.chat-tab.is-hit { animation: chatTabHit .26s cubic-bezier(.3, 1.3, .5, 1); }
@keyframes chatTabHit {
  0%   { transform: scale(1); }
  40%  { transform: scale(.94); }
  100% { transform: scale(1); }
}

/* Лента въезжает с той стороны, куда переключились. Направление и есть смысл
   этой анимации: оно показывает, где ты оказался среди трёх рынков. Поэтому
   не «проявление», которое одинаково для всех, а именно сдвиг. */
.chat-feed.is-from-right { animation: chatFeedFromRight .28s cubic-bezier(.2, .9, .3, 1) both; }
.chat-feed.is-from-left  { animation: chatFeedFromLeft  .28s cubic-bezier(.2, .9, .3, 1) both; }
@keyframes chatFeedFromRight {
  from { opacity: 0; transform: translateX(26px); }
  to   { opacity: 1; transform: none; }
}
@keyframes chatFeedFromLeft {
  from { opacity: 0; transform: translateX(-26px); }
  to   { opacity: 1; transform: none; }
}

/* Кому тяжело от движения - без сдвига и сжатия. Звук и тактильный отклик
   остаются: они несут то же сообщение, не требуя движения на экране. */
@media (prefers-reduced-motion: reduce) {
  .chat-tab.is-hit { animation: none; }
  .chat-feed.is-from-right, .chat-feed.is-from-left { animation: none; }
}

/* Кнопка «скопировать ссылку на этот чат». Внешне - как соседний колокольчик:
   обе стоят в одном ряду и делают что-то со всем чатом целиком, а не с
   сообщением, поэтому и выглядеть должны одинаково. */
.chat-share {
  flex: 0 0 auto; box-sizing: border-box; width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-dim);
  cursor: pointer; font-family: inherit;
  padding: 0; display: flex; align-items: center; justify-content: center;
}
.chat-share:active { background: var(--card); }
.chat-share svg { width: 14px; height: 14px; }

/* Вкладки чуть отодвинуты от стрелки «назад»: раньше «Садовод» почти касался
   её, и на узком экране они читались как одна кнопка. */
.chat-tabs { margin-left: 4px; }

/* ═══ Партнёрское предложение ══════════════════════════════════════════════
   Одна карточка вместо двух. Человек приходит сюда за одной цифрой - сколько
   платят, - и она должна встречать его первой, а не ждать во второй карточке
   после описания вакансии. */
.partner-offer {
  padding: 22px 18px 18px;
  border-radius: 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 4px 12px rgba(16, 24, 40, .05);
  text-align: center;
}

/* Цифра крупная: это главное, что здесь сказано. */
.partner-offer-amount {
  font-size: 46px; font-weight: 900; line-height: 1;
  color: var(--pine, #0f9d58); letter-spacing: -1px;
}
.partner-offer-lead {
  margin-top: 8px; font-size: 15px; font-weight: 700; line-height: 1.35; color: var(--fg);
}
/* Оговорка «платим всё время» - сразу под суммой, не в конце: именно она
   отличает наше предложение от разовой выплаты за приведённого человека. */
.partner-offer-sub {
  margin-top: 8px; font-size: 13px; line-height: 1.45; color: var(--fg-dim);
}

.partner-offer-sep {
  height: 1px; background: var(--border); margin: 16px -18px 14px;
}

.partner-offer-what {
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .4px; color: var(--fg-dim); margin-bottom: 8px;
}
/* Список слева, а не по центру: перечисление читается глазом по левому краю,
   центрированные пункты приходится искать заново на каждой строке. */
.partner-offer-list {
  margin: 0; padding: 0 0 0 20px; text-align: left;
  font-size: 14px; line-height: 1.6; color: var(--fg);
}
.partner-offer-list li { margin: 0; }

/* Новое сообщение в неоткрытом чате: вкладка рынка и кнопка «Чаты» на главной
   мягко пульсируют зелёным, пока человек туда не зайдёт. */
@keyframes chatNewBlink {
  0%, 100% { box-shadow: 0 0 0 0 rgba(15, 157, 88, 0); border-color: var(--border); }
  50%      { box-shadow: 0 0 0 3px rgba(15, 157, 88, .35); border-color: #0f9d58; }
}
.chat-tab.is-new:not(.is-on) { animation: chatNewBlink 1.2s ease-in-out infinite; }
.home-chats-btn.is-new { animation: chatNewBlink 1.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .chat-tab.is-new:not(.is-on), .home-chats-btn.is-new {
    animation: none; border-color: #0f9d58;
  }
}

/* Черта над вкладками чата убрана: шапка приложения и так отделена от чата
   фоном полотна, а черта только съедала высоту. */
.chat-divider { display: none !important; }

/* Крестик удаления для администратора - внутри пузыря, справа сверху.
   Пузырю даём отступ справа, чтобы крестик никогда не лёг на текст.
   Видимый кружок 20px, зона нажатия шире за счёт ::before. */
.chat-bubble.has-x { position: relative; padding-right: 30px; }
.chat-admin-x {
  position: absolute; top: 5px; right: 5px;
  width: 20px; height: 20px; border-radius: 50%; padding: 0;
  border: none; background: #dc2626; color: #fff;
  font-family: inherit; font-size: 11px; font-weight: 700; line-height: 20px;
  text-align: center; cursor: pointer;
}
.chat-admin-x::before { content: ""; position: absolute; inset: -8px; }
.chat-admin-x:active { background: #b91c1c; }

/* Компактное подтверждение (удаление сообщения в чате): одна строка вопроса
   и две небольшие кнопки. Действие частое и делает его только админ. */
.ask-card.is-compact { max-width: 260px; padding: 14px 14px 12px; }
.ask-card.is-compact .ask-title { font-size: 15px; margin-bottom: 10px; text-align: center; }
.ask-card.is-compact .ask-acts { gap: 8px; margin-top: 0; }
.ask-card.is-compact .ask-acts .link-btn {
  padding: 7px 10px; min-height: 0; height: 34px; font-size: 14px; border-radius: 9px;
}

/* Крестик удаления: не сплошной красный круг, а светло-красный фон с красным
   знаком, чуть меньше. При наведении (и нажатии на телефоне) заливается
   красным, знак белеет и крестик слегка поворачивается и растёт. */
.chat-admin-x {
  top: 6px; right: 6px; width: 16px; height: 16px; line-height: 16px;
  font-size: 9px; background: rgba(220, 38, 38, .12); color: #dc2626;
  transition: background-color .18s ease, color .18s ease, transform .22s cubic-bezier(.3, 1.4, .5, 1);
}
.chat-admin-x:hover, .chat-admin-x:focus-visible, .chat-admin-x:active {
  background: #dc2626; color: #fff; transform: scale(1.2) rotate(90deg);
}
.chat-bubble.has-x { padding-right: 26px; }
@media (prefers-reduced-motion: reduce) {
  .chat-admin-x { transition: none; }
  .chat-admin-x:hover, .chat-admin-x:active { transform: none; }
}


/* ═══ Чат в стиле Telegram ═══════════════════════════════════════════════════
   Фон - живой градиент как у обоев Telegram: четыре мягких пятна медленно
   перетекают, поверх - редкий узор точками. Пузыри: свои - салатовые, чужие -
   белые, с «хвостиком» у нижнего угла; имя автора и время - внутри пузыря. */
.chat-view {
  background-color: #cfd8b5;
  background-image:
    radial-gradient(circle at 20% 20%, rgba(219, 221, 187, .95) 0%, transparent 55%),
    radial-gradient(circle at 80% 30%, rgba(107, 165, 135, .75) 0%, transparent 55%),
    radial-gradient(circle at 30% 85%, rgba(213, 216, 141, .9) 0%, transparent 55%),
    radial-gradient(circle at 85% 80%, rgba(136, 184, 132, .85) 0%, transparent 55%);
  background-size: 220% 220%;
  animation: chatWallpaper 26s ease-in-out infinite;
}
.chat-view::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  opacity: .22;
  background-image:
    radial-gradient(rgba(40, 70, 40, .55) 1px, transparent 1.4px),
    radial-gradient(rgba(40, 70, 40, .35) 1px, transparent 1.4px);
  background-size: 34px 34px, 34px 34px;
  background-position: 0 0, 17px 17px;
}
.chat-view > * { position: relative; z-index: 1; }
@keyframes chatWallpaper {
  0%   { background-position: 0% 0%; }
  25%  { background-position: 100% 30%; }
  50%  { background-position: 70% 100%; }
  75%  { background-position: 10% 70%; }
  100% { background-position: 0% 0%; }
}
@media (prefers-reduced-motion: reduce) { .chat-view { animation: none; } }

.chat-feed { gap: 4px; }
.chat-msg { max-width: 85%; }

.chat-bubble {
  position: relative;
  padding: 6px 10px 7px 11px;
  border-radius: 16px 16px 16px 5px;
  background: #fff; color: #000;
  font: 16px/1.3 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
  box-shadow: 0 1px 1.5px rgba(0, 0, 0, .14);
}
.chat-msg.is-mine .chat-bubble {
  border-radius: 16px 16px 5px 16px;
  background: #e2ffc7; color: #000;
}
/* Хвостик у нижнего угла пузыря. */
.chat-bubble::after {
  content: ""; position: absolute; bottom: 0; left: -6px;
  width: 12px; height: 14px; background: inherit;
  clip-path: path("M12 0 C12 8 9 12 0 14 L12 14 Z");
}
.chat-msg.is-mine .chat-bubble::after {
  left: auto; right: -6px;
  clip-path: path("M0 0 C0 8 3 12 12 14 L0 14 Z");
}
.chat-bubble.is-deleted { color: #6b7280; }

.chat-author {
  display: block; margin: 0 0 1px; font-size: 14px; font-weight: 600;
  color: #3a8f5a; line-height: 1.25;
}

.chat-time-in {
  float: right; position: relative; top: 7px;
  margin: 0 -2px 0 10px; font-size: 11.5px; line-height: 1;
  color: rgba(0, 0, 0, .42); white-space: nowrap;
}
.chat-msg.is-mine .chat-time-in { color: #4fae4e; }
.chat-ticks { margin-left: 3px; letter-spacing: -3px; font-size: 11px; }

/* Разделитель дня - полупрозрачная плашка поверх обоев. */
.chat-day {
  align-self: center; margin: 8px 0 4px; padding: 3px 10px;
  border-radius: 12px; background: rgba(0, 0, 0, .22);
  color: #fff; font-size: 13px; font-weight: 500;
}

/* Подписи под пузырём читаются на обоях. */
.chat-report { color: rgba(0, 0, 0, .5); margin-left: 8px; }

/* Поле ввода и кнопка отправки - как в Telegram. */
.chat-input { background: #fff; border-color: transparent; border-radius: 20px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }
.chat-send { background: #3a9d5d; box-shadow: 0 1px 2px rgba(0, 0, 0, .18); }

/* Фон чата = фон приложения (body): тот же цвет, те же два размытых пятна
   акцентного цвета и та же медленная анимация bg-drift. Зелёные обои и узор
   точками убраны. */
.chat-view {
  background-color: var(--bg);
  background-image:
    radial-gradient(circle at 15% 8%, rgba(77, 163, 255, 0.10) 0%, transparent 45%),
    radial-gradient(circle at 85% 92%, rgba(77, 163, 255, 0.08) 0%, transparent 50%);
  background-size: 160% 160%;
  animation: bg-drift 22s ease-in-out infinite;
}
@supports (background: color-mix(in srgb, red 10%, transparent)) {
  .chat-view {
    background-image:
      radial-gradient(circle at 15% 8%, color-mix(in srgb, var(--accent) 10%, transparent) 0%, transparent 45%),
      radial-gradient(circle at 85% 92%, color-mix(in srgb, var(--accent) 8%, transparent) 0%, transparent 50%);
  }
}
.chat-view::before { display: none; }
@media (prefers-reduced-motion: reduce) { .chat-view { animation: none; } }
.chat-day { background: rgba(0, 0, 0, .06); color: var(--fg-dim); }
.chat-bubble { box-shadow: 0 1px 2px rgba(16, 24, 40, .10); }

/* Галочки: вторая наезжает на первую и видна только когда сообщение прочитано. */
.chat-ticks { display: inline-flex; margin-left: 3px; letter-spacing: 0; font-size: 12px; }
.chat-ticks .tick { display: inline-block; line-height: 1; }
.chat-ticks .tick2 { margin-left: -6px; opacity: 0; transform: scale(.6);
  transition: opacity .2s ease, transform .25s cubic-bezier(.3, 1.4, .5, 1); }
.chat-ticks.is-read .tick2 { opacity: 1; transform: none; }

/* ── Карточка поставщика, редизайн ─────────────────────────────────────────── */
.sup-card {
  background: var(--bg); border: none; border-radius: 20px; padding: 16px;
  box-shadow: 0 2px 8px rgba(20, 24, 28, .06), 0 10px 28px rgba(20, 24, 28, .07);
}
.sup-head { gap: 14px; margin-bottom: 14px; }
.sup-ava {
  width: 58px; height: 58px; font-size: 22px; color: #fff;
  background: linear-gradient(135deg, #34d399, #0f766e) center/cover no-repeat;
  box-shadow: 0 0 0 3px var(--bg), 0 0 0 5px rgba(15, 157, 88, .35);
}
.sup-ava.has-img { color: transparent; background-size: cover; background-position: center; }
.sup-name { font-size: 18px; font-weight: 700; }
.sup-sub {
  display: inline-block; margin-top: 5px; padding: 2px 9px; border-radius: 999px;
  background: rgba(15, 157, 88, .1); font-size: 12px;
}
.sup-row {
  align-items: center; gap: 10px; font-size: 14.5px; padding: 9px 0;
  border-top: 1px solid rgba(0, 0, 0, .06);
}
.sup-ico {
  width: 30px; height: 30px; border-radius: 10px; background: var(--card);
  display: inline-flex; align-items: center; justify-content: center; font-size: 15px;
}
.sup-cats { margin-top: 12px; gap: 6px; }
.sup-cat { font-size: 12.5px; padding: 3px 11px; background: rgba(15, 157, 88, .09); color: #065f46; }

/* Адрес частника: три поля вместо одной свободной строки. */
#supplierAddressField .addr-in { display: block; width: 100%; box-sizing: border-box; margin-bottom: 8px; }
#supplierAddressField .addr-row { display: flex; gap: 8px; }
#supplierAddressField .addr-row .addr-in:first-child { flex: 0 0 34%; }
#supplierAddressField .addr-row .addr-in:last-child { flex: 1 1 auto; min-width: 0; }

/* ── Чат: компактнее, аватары, «!» ─────────────────────────────────────────── */
.chat-feed { gap: 2px; }
.chat-msg { max-width: 88%; }
.chat-msg.is-tail { margin-bottom: 6px; }
.chat-bubble {
  padding: 5px 9px 6px 10px; font-size: 15px; line-height: 1.28;
  border-radius: 14px;
}
.chat-msg:not(.is-mine) .chat-bubble { border-radius: 14px 14px 14px 4px; }
.chat-msg.is-mine .chat-bubble { border-radius: 14px 14px 4px 14px; }
.chat-author { font-size: 13px; margin-bottom: 0; }
.chat-msg.is-cont .chat-author { display: none; }
.chat-time-in { font-size: 11px; top: 6px; margin-left: 8px; }
/* Хвостик только у последнего в серии. */
.chat-msg:not(.is-tail) .chat-bubble::after { display: none; }

.chat-bubble-row { align-items: flex-end; gap: 6px; }
.chat-ava {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  background: linear-gradient(135deg, #34d399, #0f766e) center/cover no-repeat;
  color: #fff; font-size: 13px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.chat-ava[style*="background-image"] { color: transparent; background-size: cover; }
.chat-msg:not(.is-tail) .chat-ava { visibility: hidden; }
.chat-msg:not(.is-mine) .chat-bubble-row .chat-bubble { margin-left: 4px; }

.chat-report { display: none; }
.chat-report-ic {
  flex: 0 0 auto; align-self: center; width: 20px; height: 20px; padding: 0;
  border-radius: 50%; border: 1px solid rgba(0, 0, 0, .12); background: rgba(255, 255, 255, .7);
  color: #9ca3af; font: 700 12px/18px inherit; text-align: center; cursor: pointer;
  opacity: .55; transition: opacity .15s, color .15s, border-color .15s;
}
.chat-report-ic:hover, .chat-report-ic:active { opacity: 1; color: #dc2626; border-color: #dc2626; }
.chat-report-ic.is-sent { color: #0f9d58; border-color: #0f9d58; opacity: 1; }

/* ── Появление нового сообщения: снизу вверх с пружиной ─────────────────────
   Пузырь выезжает из-под поля ввода, растёт от своего угла (свои - справа,
   чужие - слева), проявляется из лёгкого размытия и чуть проскакивает вверх
   перед тем, как встать. Играет у каждого нового сообщения, в том числе у
   своего после отправки. Намеренно НЕ отключается системной настройкой
   «уменьшить движение» - иначе на части iPhone анимации не было бы вовсе. */
@keyframes chatRise {
  0%   { opacity: 0; transform: translateY(46px) scale(.82); filter: blur(4px); }
  55%  { opacity: 1; transform: translateY(-5px) scale(1.02); filter: blur(0); }
  78%  { transform: translateY(2px) scale(.995); }
  100% { opacity: 1; transform: none; filter: none; }
}
.chat-msg.is-new,
.chat-msg.is-new { animation: chatRise .62s cubic-bezier(.22, .9, .3, 1) both !important; transform-origin: 0% 100%; }
.chat-msg.is-mine.is-new { transform-origin: 100% 100%; }
/* Аватар догоняет пузырь с маленькой задержкой - появление читается живее. */
@keyframes chatAvaPop {
  0%   { opacity: 0; transform: scale(.4); }
  70%  { opacity: 1; transform: scale(1.12); }
  100% { opacity: 1; transform: none; }
}
.chat-msg.is-new .chat-ava { animation: chatAvaPop .45s .12s cubic-bezier(.3, 1.4, .5, 1) both; }
@media (prefers-reduced-motion: reduce) {
  .chat-msg.is-new { animation: chatRise .62s cubic-bezier(.22, .9, .3, 1) both !important; }
}

/* Удалённое сообщение у остальных схлопывается и исчезает, а не пропадает рывком. */
/* Распад сообщения. Пузырь уходит в размытие и слегка вверх, одновременно
   схлопывая свою высоту, чтобы лента под ним сошлась без рывка. Крошки летят
   в отдельном слое поверх экрана (Кирилл 2026-09-18). */
/* Оба класса разом - штатный случай: опрос может пометить сообщение старым
   схлопыванием поверх распада. Перечислены вместе, чтобы распад не проигрывал
   по порядку в файле (.is-leaving ниже). */
.chat-msg.is-dissolving,
.chat-msg.is-dissolving.is-leaving {
  animation: none !important;
  overflow: hidden;
  pointer-events: none;
  transition: opacity .3s ease, transform .34s ease, filter .3s ease,
              max-height .34s ease .06s, margin .34s ease .06s;
  opacity: 0;
  filter: blur(7px);
  transform: translateY(-6px) scale(.96);
  max-height: 0 !important;
  margin: 0 !important;
}

.crumb-layer {
  position: fixed; inset: 0; z-index: 9999; pointer-events: none;
}
.crumb {
  position: fixed;
  display: block;
  border-radius: 50%;
  background: currentColor;
  color: var(--pine, #0e6b5b);
  opacity: .85;
  animation: crumbFly .62s cubic-bezier(.25, .7, .35, 1) forwards;
}
@keyframes crumbFly {
  0% { opacity: .85; transform: translate(0, 0) scale(1); }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(.4); }
}
@media (prefers-reduced-motion: reduce) {
  .crumb-layer { display: none; }
  .chat-msg.is-dissolving { filter: none; transition-duration: .2s; }
}

.chat-msg.is-leaving {
  animation: none !important; overflow: hidden;
  transition: opacity .25s ease, transform .25s ease, max-height .3s ease, margin .3s ease;
  opacity: 0; transform: scale(.9); max-height: 0 !important; margin: 0 !important;
}

/* Появление нового сообщения - просто плавный подъём снизу вверх, как облако:
   без пружины, без масштаба и размытия. Длинное мягкое торможение в конце. */
@keyframes chatFloatUp {
  from { opacity: 0; transform: translateY(56px); }
  to   { opacity: 1; transform: translateY(0); }
}
.chat-msg.is-new,
.chat-msg.is-mine.is-new {
  animation: chatFloatUp .7s cubic-bezier(.16, 1, .3, 1) both !important;
}
.chat-msg.is-new .chat-ava { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .chat-msg.is-new { animation: chatFloatUp .7s cubic-bezier(.16, 1, .3, 1) both !important; }
}

/* Карандаш в углу пузыря вместо крестика. */
.chat-admin-x.chat-pen {
  background: rgba(0, 0, 0, .05); color: inherit; font-size: 10px;
}
.chat-admin-x.chat-pen:hover, .chat-admin-x.chat-pen:active {
  background: rgba(15, 157, 88, .18); color: inherit; transform: scale(1.2) rotate(-12deg);
}
/* Пузырь коротко подсвечивается, когда текст сменился после правки. */
@keyframes chatEditedFlash { 0%, 100% { filter: none; } 35% { filter: brightness(.93); } }
.chat-msg.is-edited-flash .chat-bubble { animation: chatEditedFlash .7s ease; }

/* ── Меню действий с сообщением (как в Telegram) ─────────────────────────── */
.chat-act-back {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(15, 20, 25, .28);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  animation: chatActFade .18s ease both;
}
.chat-act-back.is-closing { animation: chatActFadeOut .18s ease both; }
@keyframes chatActFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes chatActFadeOut { from { opacity: 1; } to { opacity: 0; } }
.chat-act-clone {
  position: fixed !important; margin: 0 !important; box-sizing: border-box;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .22) !important;
  animation: chatActLift .22s cubic-bezier(.2, .9, .3, 1.2) both;
  pointer-events: none;
}
.chat-act-clone::after { display: none; }
@keyframes chatActLift { from { transform: scale(1); } to { transform: scale(1.03); } }
.chat-act-menu {
  position: fixed; min-width: 190px; padding: 5px;
  background: rgba(255, 255, 255, .97); border-radius: 14px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, .22);
  animation: chatActMenu .22s cubic-bezier(.2, .9, .3, 1.15) both;
}
@keyframes chatActMenu { from { opacity: 0; transform: scale(.85); } to { opacity: 1; transform: none; } }
.chat-act-item {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 12px; border: none; background: none; border-radius: 9px;
  font: 500 15px/1.2 inherit; font-family: inherit; color: var(--fg); text-align: left; cursor: pointer;
}
.chat-act-item:active, .chat-act-item:hover { background: rgba(0, 0, 0, .06); }
.chat-act-item.is-danger { color: #dc2626; }
.chat-act-ico { width: 20px; text-align: center; font-size: 16px; }

/* Кнопки на пузырях больше не нужны. */
.chat-reply-btn, .chat-report-ic, .chat-admin-x { display: none !important; }
.chat-bubble.has-x { padding-right: 9px; }
.chat-bubble { cursor: pointer; -webkit-tap-highlight-color: transparent; }

/* Свайп влево: стрелка ответа проступает справа. */
.chat-msg::before, .chat-msg.is-mine::before { left: auto; right: -30px; }
.chat-act-holder { position: static; max-width: none; animation: none !important; transform: none !important; }
.chat-act-holder::before { display: none; }

/* ── Контурные монохромные иконки ──────────────────────────────────────────── */
.line-ico {
  width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; display: block;
}
.chat-act-ico { width: 20px; display: inline-flex; justify-content: center; color: #6b7280; }
.chat-act-item.is-danger .chat-act-ico { color: #dc2626; }
.chat-act-item { gap: 14px; }
.chat-sound, .chat-back, .chat-share { color: var(--fg); }
.chat-sound .line-ico, .chat-back .line-ico { width: 16px; height: 16px; }
.chat-sound.is-off { opacity: 1; color: var(--fg-dim); }
.chat-sound:active .line-ico { transform: rotate(-14deg); }
.chat-sound .line-ico { transition: transform .2s ease; transform-origin: 50% 10%; }

/* Колокольчик выключен: перечёркивание идёт справа сверху налево вниз. */
.line-ico.is-mirror { transform: scaleX(-1); }
.chat-sound:active .line-ico.is-mirror { transform: scaleX(-1) rotate(-14deg); }

/* Контурный значок внутри кнопок и строки статистики. */
.ico-slot { display: inline-flex; vertical-align: -3px; margin-right: 6px; line-height: 0; }
.ico-slot .line-ico { width: 1.1em; height: 1.1em; }
.home-stats-item .ico-slot { margin-right: 4px; vertical-align: -2px; color: var(--fg-dim); }
.home-stats-item .ico-slot .line-ico { width: 14px; height: 14px; }
.user-tab .ico-slot .line-ico { width: 14px; height: 14px; }
#adminOnlineBtn .ico-slot { color: #16a34a; }
#adminBlockedBtn .ico-slot { color: #dc2626; }
#profileDeleteBtn .ico-slot, #adminBootLogClearBtn .ico-slot { color: currentColor; }

/* ── Кнопка «вниз к новым» ─────────────────────────────────────────────────── */
.chat-down {
  position: absolute; right: 2px; bottom: calc(100% + 10px); z-index: 3;
  width: 42px; height: 42px; border-radius: 50%; padding: 0; border: none;
  background: #fff; color: var(--fg);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .18);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  animation: chatDownIn .28s cubic-bezier(.16, 1, .3, 1) both;
}
.chat-down.hidden { display: none; }
.chat-down .line-ico { width: 22px; height: 22px; }
.chat-down:active { transform: scale(.92); }
@keyframes chatDownIn { from { opacity: 0; transform: translateY(14px) scale(.8); } to { opacity: 1; transform: none; } }
.chat-down-count {
  position: absolute; top: -6px; left: 50%; transform: translateX(-50%);
  min-width: 20px; height: 20px; padding: 0 5px; box-sizing: border-box; border-radius: 10px;
  background: #3a9d5d; color: #fff; font-size: 11.5px; font-weight: 700; line-height: 20px; text-align: center;
}
.chat-down-count.hidden { display: none; }

/* Кнопка «вниз»: всегда в разметке, состояние - класс is-up. Выезжает снизу
   и уезжает вниз, плавно в обе стороны. */
.chat-down, .chat-down.hidden {
  display: flex; animation: none;
  opacity: 0; transform: translateY(26px) scale(.7); pointer-events: none;
  transition: opacity .25s ease, transform .38s cubic-bezier(.16, 1, .3, 1);
}
.chat-down.is-up { opacity: 1; transform: none; pointer-events: auto; }
.chat-down.is-up:active { transform: scale(.92); }
.chat-down-count, .chat-down-count.hidden {
  display: block; opacity: 0; transform: translateX(-50%) scale(.4);
  transition: opacity .2s ease, transform .3s cubic-bezier(.3, 1.5, .5, 1);
}
.chat-down-count.is-on { opacity: 1; transform: translateX(-50%) scale(1); }

/* Водяной знак карточек админки контуром. */
.card-mark.card-mark-line { font-size: 0; right: -8px; top: -8px; opacity: .10; color: var(--fg); }
.card-mark.card-mark-line .line-ico { width: 64px; height: 64px; stroke-width: 1.6; }
.earn-card-title .ico-slot { color: var(--fg-dim); }
.quick-emoji .ico-slot { margin-right: 0; }
.quick-emoji .ico-slot .line-ico { width: 16px; height: 16px; }
.home-chats-btn .ico-slot .line-ico { width: 18px; height: 18px; }
.bulb-blink .ico-slot { margin-right: 6px; }

/* ═══ «Заработать» и «Пополнить»: орбита света + подъём ═══════════════════════
   При наведении (на телефоне - пока палец на кнопке) по контуру кнопки бежит
   световой луч (вращающийся конический градиент под внутренней «крышкой»),
   кнопка приподнимается и светится. У «Заработать» внутри снизу поднимается
   зелёная «жидкость», у «Пополнить» по кнопке проходит золотая вспышка. */
.home-balance-action {
  --b: var(--stone-soft);
  position: relative; isolation: isolate; overflow: hidden;
  transition: transform .35s cubic-bezier(.16, 1, .3, 1), box-shadow .35s ease, color .3s ease;
}
.home-balance-action.primary { --b: var(--pine); }
.home-balance-action::before {
  content: ""; position: absolute; left: 50%; top: 50%; z-index: -2;
  width: 260%; aspect-ratio: 1; pointer-events: none;
  background: conic-gradient(from 0deg, transparent 0 58%, rgba(52, 211, 153, .0) 62%,
              #34d399 76%, #ecfdf5 81%, #34d399 86%, transparent 92%);
  transform: translate(-50%, -50%) rotate(0deg);
  opacity: 0; transition: opacity .25s ease;
}
.home-balance-action.primary::before {
  background: conic-gradient(from 0deg, transparent 0 58%, #fbbf24 76%, #fff7d6 81%, #fbbf24 86%, transparent 92%);
}
.home-balance-action::after {
  content: ""; position: absolute; inset: 2px; z-index: -1; pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(to top, rgba(52, 211, 153, .38) 0 50%, var(--b) 50% 100%);
  background-size: 100% 200%; background-position: 0 0;
  transition: background-position .6s cubic-bezier(.16, 1, .3, 1);
}
.home-balance-action.primary::after {
  background:
    linear-gradient(115deg, transparent 30%, rgba(255, 236, 170, .55) 48%, transparent 66%) -150% 0 / 60% 100% no-repeat,
    var(--b);
  transition: background-position .75s cubic-bezier(.16, 1, .3, 1);
}
@keyframes balanceOrbit { to { transform: translate(-50%, -50%) rotate(360deg); } }

@media (hover: hover) {
  .home-balance-action:hover { transform: translateY(-2px); }
  .home-balance-action:hover::before { opacity: 1; animation: balanceOrbit 1.6s linear infinite; }
  .home-balance-action:not(.primary):hover { box-shadow: 0 6px 18px rgba(52, 211, 153, .35); }
  .home-balance-action:not(.primary):hover::after { background-position: 0 100%; }
  .home-balance-action.primary:hover { box-shadow: 0 6px 18px rgba(251, 191, 36, .35); filter: none; }
  .home-balance-action.primary:hover::after { background-position: 250% 0, 0 0; }
}
.home-balance-action:active { transform: translateY(0) scale(.96); }
.home-balance-action:active::before { opacity: 1; animation: balanceOrbit 1.2s linear infinite; }
.home-balance-action:not(.primary):active::after { background-position: 0 100%; }
.home-balance-action.primary:active::after { background-position: 250% 0, 0 0; }
@media (prefers-reduced-motion: reduce) {
  .home-balance-action:hover::before, .home-balance-action:active::before { animation: none; opacity: .8; }
}

/* ═══ Шапка чата: отклик на наведение и нажатие ═══════════════════════════════ */
.chat-back, .chat-sound, .chat-share {
  transition: transform .3s cubic-bezier(.16, 1, .3, 1), box-shadow .3s ease, border-color .3s ease, background-color .3s ease;
}
.chat-back .line-ico, .chat-share svg { transition: transform .35s cubic-bezier(.16, 1, .3, 1); }

/* Назад: стрелка «тянет» влево. */
@keyframes chatBackNudge { 0%, 100% { transform: translateX(0); } 45% { transform: translateX(-4px); } }
/* Колокольчик: качается, как при звонке. */
@keyframes chatBellRing {
  0%, 100% { transform: rotate(0); }
  15% { transform: rotate(16deg); } 30% { transform: rotate(-14deg); }
  45% { transform: rotate(10deg); } 60% { transform: rotate(-6deg); } 75% { transform: rotate(3deg); }
}
@keyframes chatBellRingM {
  0%, 100% { transform: scaleX(-1) rotate(0); }
  15% { transform: scaleX(-1) rotate(16deg); } 30% { transform: scaleX(-1) rotate(-14deg); }
  45% { transform: scaleX(-1) rotate(10deg); } 60% { transform: scaleX(-1) rotate(-6deg); }
}
/* Вкладка рынка: приподнимается, по низу пробегает зелёная полоска. */
.chat-tab { transition: transform .3s cubic-bezier(.16, 1, .3, 1), box-shadow .3s ease, border-color .3s ease; }
.chat-tab::after {
  content: ""; position: absolute; left: 12%; right: 12%; bottom: 2px; height: 2px; border-radius: 2px;
  background: var(--pine, #0f9d58); transform: scaleX(0); transform-origin: left;
  transition: transform .35s cubic-bezier(.16, 1, .3, 1); pointer-events: none;
}

@media (hover: hover) {
  .chat-back:hover, .chat-sound:hover, .chat-share:hover {
    transform: translateY(-2px); border-color: var(--pine, #0f9d58);
    box-shadow: 0 5px 14px rgba(15, 107, 91, .18);
  }
  .chat-back:hover .line-ico { animation: chatBackNudge .7s ease-in-out infinite; }
  .chat-sound:hover .line-ico { animation: chatBellRing .9s ease-in-out; transform-origin: 50% 8%; }
  .chat-sound:hover .line-ico.is-mirror { animation: chatBellRingM .9s ease-in-out; }
  .chat-share:hover svg { transform: translate(1px, -1px) scale(1.12); }
  .chat-tab:not(.is-on):hover { transform: translateY(-2px); box-shadow: 0 5px 14px rgba(15, 107, 91, .14); border-color: rgba(15, 107, 91, .35); }
  .chat-tab:not(.is-on):hover::after { transform: scaleX(1); }
}
.chat-back:active, .chat-sound:active, .chat-share:active { transform: scale(.9); }
.chat-tab:active { transform: scale(.96); }
@media (prefers-reduced-motion: reduce) {
  .chat-back:hover .line-ico, .chat-sound:hover .line-ico { animation: none; }
}

/* Кнопка копирования в окне приглашения: контурные квадратики -> галочка. */
.invite-copy.copy-btn { display: flex; align-items: center; justify-content: center; padding: 0;
  transition: border-color .2s ease, background-color .2s ease, transform .3s cubic-bezier(.16, 1, .3, 1); }
.invite-copy .copy-ico { width: 18px; height: 18px; }
@media (hover: hover) {
  .invite-copy.copy-btn:hover { border-color: var(--pine, #0f9d58); transform: translateY(-1px); }
  .invite-copy.copy-btn:hover .copy-ico .ico-copy { transform: translate(1px, -1px) scale(1.08); }
}

/* «Заработать»: без луча по контуру - только заливка цветом снизу и мягкое свечение.
   «Пополнить» не трогаем. */
.home-balance-action:not(.primary)::before { display: none; }
.home-balance-action:not(.primary)::after {
  inset: 0;
  background: linear-gradient(to top, rgba(52, 211, 153, .42) 0 50%, var(--b) 50% 100%);
  background-size: 100% 200%; background-position: 0 0;
  transition: background-position .55s cubic-bezier(.16, 1, .3, 1);
}
@media (hover: hover) {
  .home-balance-action:not(.primary):hover { transform: translateY(-1px); box-shadow: 0 0 0 1px rgba(52, 211, 153, .35), 0 4px 16px rgba(52, 211, 153, .38); }
  .home-balance-action:not(.primary):hover::after { background-position: 0 100%; }
}
.home-balance-action:not(.primary):active { box-shadow: 0 0 14px rgba(52, 211, 153, .4); }

.projects-heading .ico-slot { color: var(--fg-dim); margin-right: 8px; }
.projects-heading .ico-slot .line-ico { width: 20px; height: 20px; }

/* Медленный тост (бонус за вход): все фазы в 3 раза дольше. */
.toast.is-slow { animation-duration: 1.02s; }
.toast.is-slow.is-out { animation-duration: .9s; }

/* Черта над вкладками чата возвращена: без неё строка вкладок и кнопок
   прилипала к шапке приложения и визуально уезжала под неё. */
.chat-divider { display: block !important; margin: 0 0 8px; }

/* ═══ Шапка чата: откат неудачных анимаций и место под названия ══════════════
   1) Полоска под вкладкой при наведении ложилась прямо на «онлайн N» - убрана.
      Подъём вкладок и кнопок тоже убран: на двухстрочной вкладке высотой 30px
      он выглядел как дёрганье и упирался в черту сверху.
   2) Названия рынков обрезались («Южные Во…»): три вкладки делили ширину
      поровну, хотя «Садовод» короче «Южных Ворот». Теперь ширина по содержимому,
      а боковые поля панели чата чуть уже - на узком телефоне названия влезают. */
.chat-tab::after { display: none !important; }
@media (hover: hover) {
  .chat-tab:not(.is-on):hover { transform: none; box-shadow: none; border-color: var(--pine, #0f9d58); }
  .chat-back:hover, .chat-sound:hover, .chat-share:hover { transform: none; box-shadow: none; }
  .chat-back:hover .line-ico { animation: none; }
}
.chat-tab { flex: 1 1 auto; padding: 0 3px; letter-spacing: -0.3px; }
.chat-view { padding-left: 10px; padding-right: 10px; }
.chat-bar { gap: 5px; }
.chat-tabs { gap: 5px; margin-left: 2px; }

/* ═══ «Назад» везде одинаково ═════════════════════════════════════════════════
   Круглая в чате и таблетка «← Назад» на экранах: при наведении кнопка
   приподнимается, получает зелёную обводку и тень, а стрелка ритмично тянет влево. */
.back-arr { display: inline-flex; line-height: 0; }
.back-arr .line-ico { width: 16px; height: 16px; stroke-width: 2.4; }
.back-btn, .chat-back {
  transition: transform .3s cubic-bezier(.16, 1, .3, 1), box-shadow .3s ease, border-color .3s ease;
}
.back-btn { box-shadow: inset 0 0 0 1px transparent; }
@media (hover: hover) {
  .back-btn:hover {
    transform: translateY(-2px);
    box-shadow: inset 0 0 0 1px var(--pine, #0f9d58), 0 5px 14px rgba(15, 107, 91, .18);
  }
  .chat-back:hover {
    transform: translateY(-2px); border-color: var(--pine, #0f9d58);
    box-shadow: 0 5px 14px rgba(15, 107, 91, .18);
  }
  .back-btn:hover .back-arr .line-ico,
  .chat-back:hover .line-ico { animation: chatBackNudge .7s ease-in-out infinite; }
}
.back-btn:active, .chat-back:active { transform: scale(.93); }
@media (prefers-reduced-motion: reduce) {
  .back-btn:hover .back-arr .line-ico, .chat-back:hover .line-ico { animation: none; }
}

/* ═══ Больше места ленте чата ═════════════════════════════════════════════════
   Шапка и подвал ужаты по высоте: строка «Назад / рынки / звук» ближе к черте,
   поле ввода и кнопка ниже, а пустая строка под полем (для ошибок) больше не
   занимает 24px, когда в ней ничего нет. */
.chat-head { padding-top: 2px; }
.chat-divider { margin: 0 0 5px !important; }
.chat-foot { padding-top: 4px; }
.chat-composer { margin-top: 0; gap: 6px; }
.chat-input { padding: 8px 13px; border-radius: 18px; }
.chat-send { width: 38px; height: 38px; font-size: 18px; }
.chat-note { margin-top: 4px; min-height: 0; }
.chat-note:empty { display: none; }
.chat-view { padding-bottom: calc(4px + var(--safe-b)); }
.chat-reply-bar { padding: 6px 10px; margin-bottom: 5px; }

/* Шапка чата: запас сверху под черту (кнопки при наведении приподнимаются на 2px
   и упирались в неё) и зазор снизу - иначе прокручиваемые сообщения подъезжали
   вплотную под вкладки и выглядели обрезанными. */
.chat-head { padding-top: 0; padding-bottom: 6px; }
.chat-head .chat-divider { margin: 0 0 8px !important; }
/* Верхний край ленты мягко растворяется: сообщение, уходящее под вкладки при
   прокрутке, не выглядит обрезанным ножом. */
.chat-feed {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14px);
          mask-image: linear-gradient(to bottom, transparent 0, #000 14px);
}

/* ── «Мои баллы»: сводка ─────────────────────────────────────────────────── */
.points-summary { margin: 4px 0 6px; }
.ps-cards { display: grid; grid-template-columns: 1.3fr 1fr 1fr; gap: 8px; }
.ps-card {
  background: var(--bg); border-radius: 14px; padding: 12px 10px; text-align: center;
  box-shadow: 0 1px 3px rgba(20, 24, 28, .06);
}
.ps-card.is-main { background: #ecfdf5; }
.ps-num { font-size: 20px; font-weight: 800; color: var(--pine, #0E6B5B); letter-spacing: -0.3px; }
.ps-card:not(.is-main) .ps-num { font-size: 16px; color: var(--fg); }
.ps-cap { font-size: 12px; color: var(--fg-dim); margin-top: 2px; }
.points-section-title { font-size: 13px; font-weight: 700; color: var(--fg-dim); margin: 16px 2px 8px; text-transform: uppercase; letter-spacing: .4px; }
.ps-empty { font-size: 14px; color: var(--fg-dim); padding: 4px 2px; }
.ps-list { background: var(--bg); border-radius: 14px; overflow: hidden; }
.ps-row { display: flex; justify-content: space-between; gap: 10px; padding: 11px 14px; font-size: 14.5px; }
.ps-row + .ps-row { border-top: 1px solid rgba(0, 0, 0, .06); }
.ps-row b { color: var(--pine, #0E6B5B); white-space: nowrap; }
.ps-x { color: var(--fg-dim); font-size: 13px; }
.ps-bars { display: flex; gap: 6px; align-items: flex-end; background: var(--bg); border-radius: 14px; padding: 12px 10px 8px; }
.ps-bar { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.ps-bar-val { font-size: 11px; font-weight: 700; color: var(--fg-dim); min-height: 13px; }
.ps-bar-track { width: 100%; max-width: 26px; height: 70px; display: flex; align-items: flex-end; }
.ps-bar-fill { width: 100%; border-radius: 6px 6px 3px 3px; background: rgba(15, 107, 91, .28);
  animation: psGrow .6s cubic-bezier(.16, 1, .3, 1) both; transform-origin: bottom; }
.ps-bar.is-today .ps-bar-fill { background: var(--pine, #0E6B5B); }
.ps-bar.is-today .ps-bar-val, .ps-bar.is-today .ps-bar-day { color: var(--pine, #0E6B5B); }
.ps-bar-day { font-size: 11.5px; color: var(--fg-dim); }
@keyframes psGrow { from { transform: scaleY(0); } to { transform: none; } }
#earnGetLinkBtn .ico-slot .line-ico { width: 18px; height: 18px; }

/* Кнопки на фото аватара: контурные значки. */
.viewer-tile { display: inline-flex; align-items: center; justify-content: center; }
.viewer-tile .ico-slot { margin-right: 6px; }
.viewer-tile .ico-slot .line-ico { width: 17px; height: 17px; }

/* Контурные значки, подставленные вместо эмодзи, внутри любых подписей. */
button .ico-slot, a .ico-slot { vertical-align: -3px; }
.tag .ico-slot, .hint .ico-slot, .sup-row .ico-slot { margin-right: 4px; }
.tag .ico-slot .line-ico, .hint .ico-slot .line-ico { width: 13px; height: 13px; }
.pitch-topbar-title .ico-slot { color: var(--fg-dim); }

/* ── Страница товара: строка «Назад» + цена и счётчики ─────────────────────── */
.product-topbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.product-topbar .back-btn { margin: 0; flex: 0 0 auto; }
.product-topstats { display: flex; align-items: center; gap: 12px; min-width: 0; color: var(--fg-dim); font-size: 14px; }
.pts-price { font-size: 17px; font-weight: 800; color: var(--pine, #0E6B5B); white-space: nowrap; }
.pts-item { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; font-weight: 600; }
.pts-item .line-ico { width: 16px; height: 16px; }
.product-title-top { margin: 0 0 10px; }
.meta-ico { display: inline-flex; vertical-align: -2px; margin-right: 3px; }
.meta-ico .line-ico { width: 14px; height: 14px; }
#productBody > .respond-video { margin-bottom: 12px; }

/* ── Полоса загрузки при запуске ────────────────────────────────────────────── */
.boot-bar { max-width: 260px; margin: 64px auto 0; text-align: center; transition: opacity .35s ease, transform .35s ease; }
.boot-bar.is-gone { opacity: 0; transform: translateY(-6px); pointer-events: none; height: 0; margin: 0 auto; overflow: hidden; }
.boot-bar-pct { font-size: 34px; font-weight: 800; color: var(--pine, #0E6B5B); letter-spacing: -0.5px; font-variant-numeric: tabular-nums; }
.boot-bar-track {
  position: relative; height: 8px; margin-top: 12px; border-radius: 99px; overflow: hidden;
  background: rgba(14, 107, 91, .12);
}
.boot-bar-fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0; border-radius: 99px;
  background: linear-gradient(90deg, #34d399, #0E6B5B);
  box-shadow: 0 0 12px rgba(52, 211, 153, .55);
}
/* Блик бежит по заполненной части - полоса выглядит живой, даже когда почти стоит. */
.boot-bar-fill::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 60px; left: -60px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .65), transparent);
  animation: bootShine 1.1s linear infinite;
}
@keyframes bootShine { to { left: 100%; } }
.boot-bar + #loading { margin-top: 10px; }
.boot-bar.is-gone + #loading { margin-top: 0; }
.boot-bar + #loading.state-msg { padding-top: 12px; }

/* ── Подписки внутри карточек «Заработать» ─────────────────────────────────── */
.earn-subs { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; position: relative; z-index: 1;
  animation: earnSubsIn .3s cubic-bezier(.16, 1, .3, 1) both; }
@keyframes earnSubsIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.earn-subs-wait { font-size: 14px; color: var(--fg-dim); padding: 6px 0; }
.earn-sub {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 8px 8px 12px; border-radius: 12px; background: var(--card);
}
.earn-sub-title { font-size: 15px; font-weight: 600; color: var(--fg); min-width: 0; }
.earn-sub-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
  padding: 7px 14px; border-radius: 10px; border: 1px solid var(--pine, #0E6B5B);
  background: #fff; color: var(--pine, #0E6B5B); font: 600 14px/1 inherit; font-family: inherit; cursor: pointer;
  transition: background-color .3s ease, color .3s ease, border-color .3s ease;
}
.earn-sub-btn.is-on { background: #16a34a; border-color: #16a34a; color: #fff; }
.earn-sub-btn .line-ico { width: 15px; height: 15px; }
.earn-card.is-open .earn-card-go { color: var(--fg-dim); }

.earn-card-daily { cursor: pointer; -webkit-tap-highlight-color: transparent; transition: transform .2s ease; }
.earn-card-daily:active { transform: scale(.97); }

/* Загрузка - отдельный непрозрачный слой поверх ВСЕГО приложения (шапки тоже):
   интерфейс собирается под ним невидимо и появляется только когда слой растаял.
   Раньше полоса стояла в потоке страницы, и экран подъезжал под неё вперемешку. */
.boot-bar {
  position: fixed; inset: 0; z-index: 500; max-width: none; margin: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: var(--bg-page, #f5f5f7); padding: 0 24px;
  transition: opacity .35s ease;
}
.boot-bar-track, .boot-bar-pct { width: 100%; max-width: 260px; }
.boot-bar-cap { margin-top: 14px; font-size: 14px; color: var(--fg-dim); }
.boot-bar.is-gone { opacity: 0; transform: none; height: auto; margin: 0; overflow: visible; pointer-events: none; }
/* Пока слой загрузки на месте, старую надпись «Открываем…» под ним не показываем. */
.boot-bar + #loading { visibility: hidden; margin-top: 0; }
.boot-bar.is-gone + #loading { visibility: visible; }

.product-title-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 0 0 10px; }
.product-title-row .product-title-top { margin: 0; min-width: 0; }
.product-title-price { flex: 0 0 auto; font-size: 19px; font-weight: 800; color: var(--pine, #0E6B5B); white-space: nowrap; }

.product-meta { line-height: 1.5; }
.product-meta-line + .product-meta-line { margin-top: 3px; }
.meta-hidden { color: var(--fg-dim); opacity: .75; font-size: 13px; }

#productBody .product-desc { margin-bottom: 14px; }
#productBody .product-meta:last-child { margin-bottom: 14px; }

/* Переписка с поставщиком: шапка и предупреждение компактнее, лента занимает всё. */
#dlgView .dlg-head { padding-top: 6px; }
#dlgView .dlg-warn { margin: 0 0 6px; padding: 8px 12px; font-size: 13px; line-height: 1.35; border-radius: 12px; }
#dlgView .chat-feed { flex: 1 1 auto; min-height: 0; }

/* Непрочитанные переписки: счётчик на «Мой профиль» и «Мои сообщения». */
.dlg-unread-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; margin-left: 6px; box-sizing: border-box;
  border-radius: 9px; background: #16a34a; color: #fff; font-size: 11px; font-weight: 700; line-height: 1;
}
.quick-btn.is-new, #profileMessagesBtn.is-new { animation: chatNewBlink 1.2s ease-in-out infinite; }

/* Метки на фото плиток: контурный значок + текст/число. */
.tile-badge { display: inline-flex; align-items: center; gap: 4px; }
.tile-badge .line-ico { width: 13px; height: 13px; stroke-width: 2.2; flex: 0 0 auto; }

/* Метка обсуждения на фото - в тон остальным меткам, без синей заливки. */
.tile-badge-offers { background: rgba(17, 24, 39, .72); }

.hint.is-warn { color: #dc2626; }

/* Стрелка «ещё» на купленном баннере: справа снизу, полупрозрачная, не спорит с рекламой. */
.ad-banner-more {
  position: absolute; right: 8px; bottom: 8px; z-index: 3;
  width: 30px; height: 30px; padding: 0; border: none; border-radius: 50%;
  background: rgba(0, 0, 0, .28); color: #fff;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  opacity: .8; transition: opacity .2s ease, transform .25s cubic-bezier(.16, 1, .3, 1);
}
.ad-banner-more.hidden { display: none; }
.ad-banner-more .line-ico { width: 18px; height: 18px; stroke-width: 2.4; }
.ad-banner-more:active { transform: scale(.9); opacity: 1; }
@media (hover: hover) { .ad-banner-more:hover { opacity: 1; transform: translateY(1px); } }

/* «Заказать рекламу» - маленькая полупрозрачная плашка справа снизу. */
.ad-banner-more {
  width: auto; height: 24px; padding: 0 10px; border-radius: 12px;
  font: 600 11.5px/24px inherit; font-family: inherit; letter-spacing: .2px; white-space: nowrap;
}

/* Квадратная, еле видная кнопка + выезжающая рядом плашка «Заказать рекламу». */
.ad-banner-more {
  width: 26px; height: 26px; padding: 0; border-radius: 8px;
  background: rgba(255, 255, 255, .14); color: rgba(255, 255, 255, .85);
  border: 1px solid rgba(255, 255, 255, .18);
  opacity: .55; font-size: 0;
}
.ad-banner-more .line-ico { width: 15px; height: 15px; stroke-width: 2.4; transition: transform .3s cubic-bezier(.16, 1, .3, 1); }
.ad-banner-more.is-open { opacity: 1; background: rgba(255, 255, 255, .22); }
.ad-banner-more.is-open .line-ico { transform: rotate(180deg); }
.ad-banner-order {
  position: absolute; right: 40px; bottom: 8px; z-index: 3;
  height: 26px; padding: 0 12px; border: none; border-radius: 8px;
  background: rgba(255, 255, 255, .92); color: #0f172a;
  font: 600 12.5px/26px inherit; font-family: inherit; white-space: nowrap; cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .25);
  opacity: 0; transform: translateX(10px) scale(.92); pointer-events: none;
  transition: opacity .22s ease, transform .3s cubic-bezier(.16, 1, .3, 1);
}
.ad-banner-order.is-open { opacity: 1; transform: none; pointer-events: auto; }
.ad-banner-order:active { transform: scale(.95); }

/* Плашка «Заказать рекламу» выезжает ВНИЗ из-под кнопки, под край баннера. */
.ad-banner-order {
  right: 8px; bottom: auto; top: calc(100% - 8px); z-index: 6;
  transform: translateY(-8px) scale(.94); transform-origin: top right;
}
.ad-banner-order.is-open { transform: translateY(6px); }
.ad-banner-order.is-open:active { transform: translateY(6px) scale(.95); }

/* Пример баннера = настоящий баннер, уменьшенный ровно по ширине: текст, подпись и отступы
   масштабируются одним коэффициентом (--ad-pv-scale считает скрипт). */
.ad-preview-banner .ad-banner-title { font-size: calc(var(--ad-title-size, 26px) * var(--ad-pv-scale, .9)); }
.ad-preview-banner .ad-banner-sub   { font-size: calc(var(--ad-title-size, 26px) * .52 * var(--ad-pv-scale, .9)); }
.ad-preview-banner .ad-banner-body  {
  padding: calc(18px * var(--ad-pv-scale, .9)) calc(18px * var(--ad-pv-scale, .9))
           calc(18px * var(--ad-pv-scale, .9)) calc(20px * var(--ad-pv-scale, .9));
}


/* ═══ Новые движения баннера (2026-09-17) ═════════════════════════════════════ */

/* ЖИДКИЙ МЕТАЛЛ. Буквы залиты хромом, по ним непрерывно перетекает блик, будто металл
   жидкий. Тень рисуем фильтром, а не text-shadow: при прозрачной заливке букв обычная тень
   просвечивала бы сквозь них и делала хром грязным. */
.ad-tanim-metal .ad-banner-title {
  background: linear-gradient(105deg, #b8c2cc 0%, #ffffff 14%, #7d8a97 28%, #f4f7fa 42%,
              #9aa6b2 56%, #ffffff 70%, #aab4be 84%, #eef2f6 100%);
  background-size: 260% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  text-shadow: none !important;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .55)) drop-shadow(0 3px 8px rgba(0, 0, 0, .35));
  animation: ad-metal-flow calc(var(--ad-t, 1s) * 3.2) linear infinite;
}
@keyframes ad-metal-flow { from { background-position: 0% 0; } to { background-position: -260% 0; } }
.ad-tanim-metal .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .8) ease-out both; }

/* МАГНИТ. Буквы разбросаны далеко и крупно, затем резко притягиваются на место и
   «прилипают» с маленьким отскоком. Разброс - тот же случайный --dx/--dy, что у «Сборки»,
   только втрое дальше и с перелётом через точку назначения. */
.ad-tanim-magnet .ad-banner-title span {
  display: inline-block; white-space: pre;
  animation: ad-magnet calc(var(--ad-t, 1s) * 1.1) cubic-bezier(.55, 0, .1, 1) both;
  animation-delay: calc(var(--i, 0) * var(--ad-t, 1s) * .012);
}
@keyframes ad-magnet {
  0%   { opacity: 0; transform: translate(calc(var(--dx, 0px) * 3), calc(var(--dy, 0px) * 3)) rotate(var(--dr, 0deg)) scale(1.8); filter: blur(3px); }
  55%  { opacity: 1; filter: blur(0); }
  72%  { transform: translate(calc(var(--dx, 0px) * -.06), calc(var(--dy, 0px) * -.06)) scale(.92); }
  86%  { transform: translate(calc(var(--dx, 0px) * .02), calc(var(--dy, 0px) * .02)) scale(1.04); }
  100% { opacity: 1; transform: none; filter: none; }
}
.ad-tanim-magnet .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .7) ease-out calc(var(--ad-t, 1s) * .8) both; }

/* СТЕКЛО. Над текстом стоит полоса матового стекла; она уезжает вбок, и за ней текст
   проявляется из размытия. Раз в несколько секунд стекло проходит снова. */
.ad-tanim-glass .ad-banner-body { position: relative; overflow: hidden; }
.ad-tanim-glass .ad-banner-body::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 60%;
  pointer-events: none;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .16) 30%,
              rgba(255, 255, 255, .22) 50%, rgba(255, 255, 255, .16) 70%, rgba(255, 255, 255, 0));
  -webkit-backdrop-filter: blur(7px) saturate(1.3); backdrop-filter: blur(7px) saturate(1.3);
  border-left: 1px solid rgba(255, 255, 255, .35);
  animation: ad-glass-sweep calc(var(--ad-t, 1s) * 5) cubic-bezier(.65, 0, .35, 1) infinite;
}
@keyframes ad-glass-sweep {
  0%   { transform: translateX(-10%); opacity: 1; }
  38%  { transform: translateX(190%); opacity: 1; }
  39%, 100% { transform: translateX(190%); opacity: 0; }
}
.ad-tanim-glass .ad-banner-title,
.ad-tanim-glass .ad-banner-sub { animation: ad-glass-text calc(var(--ad-t, 1s) * 5) ease-out infinite; }
@keyframes ad-glass-text {
  0%   { filter: blur(6px); opacity: .35; }
  30%  { filter: blur(0); opacity: 1; }
  100% { filter: blur(0); opacity: 1; }
}

/* ПАРАЛЛАКС. Снимок медленно ходит по кругу, а текст - чуть-чуть в обратную сторону:
   глаз читает это как два слоя на разной глубине, баннер становится объёмным. */
.ad-banim-parallax .ad-banner-bg {
  animation: ad-parallax-back calc(var(--ad-t, 1s) * 12) ease-in-out infinite;
}
.ad-banim-parallax .ad-banner-body {
  animation: ad-parallax-front calc(var(--ad-t, 1s) * 12) ease-in-out infinite;
}
@keyframes ad-parallax-back {
  0%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)) translate(-2.5%, -1.5%); }
  25%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)) translate(2.5%, -1.5%); }
  50%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)) translate(2.5%, 1.5%); }
  75%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)) translate(-2.5%, 1.5%); }
}
@keyframes ad-parallax-front {
  0%, 100% { transform: translate(1.2%, .8%); }
  25%      { transform: translate(-1.2%, .8%); }
  50%      { transform: translate(-1.2%, -.8%); }
  75%      { transform: translate(1.2%, -.8%); }
}

/* СВЕТОВОЙ ЛУЧ. Узкий луч прожектора пересекает снимок по диагонали сверху вниз, как свет
   на витрине, потом пауза. Смешивание «экран» - луч осветляет фото, а не закрашивает. */
.ad-banim-beam::before {
  content: ""; position: absolute; z-index: 1; pointer-events: none;
  top: -60%; left: -30%; width: 22%; height: 220%;
  background: linear-gradient(90deg, transparent, rgba(255, 244, 214, .08) 20%,
              rgba(255, 250, 235, .55) 50%, rgba(255, 244, 214, .08) 80%, transparent);
  mix-blend-mode: screen;
  filter: blur(4px);
  transform: rotate(28deg);
  animation: ad-beam calc(var(--ad-t, 1s) * 7) cubic-bezier(.45, 0, .25, 1) infinite;
}
@keyframes ad-beam {
  0%        { left: -30%; opacity: 0; }
  6%        { opacity: 1; }
  45%       { left: 115%; opacity: 1; }
  48%, 100% { left: 115%; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ad-tanim-metal .ad-banner-title,
  .ad-tanim-magnet .ad-banner-title span,
  .ad-tanim-glass .ad-banner-body::after,
  .ad-tanim-glass .ad-banner-title, .ad-tanim-glass .ad-banner-sub,
  .ad-banim-parallax .ad-banner-bg, .ad-banim-parallax .ad-banner-body,
  .ad-banim-beam::before { animation: none; }
}

/* Легенда графика рекламы: просмотры синие, переходы красные, обе - сплошной линией. */
.dot-visits { background: #2563eb !important; }
.dot-clicks { background: #e5484d !important; }


/* ═══ Переработка движений (2026-09-17) ═══════════════════════════════════════ */

/* ПЕЧАТЬ - заново. Заголовок целиком открывается слева направо ступенями, по одной букве
   (--n знаков). После набора справа мигает курсор - это рамка у края текста, её тоже
   открывает та же маска, поэтому курсор появляется ровно в конце набора. */
.ad-tanim-type .ad-banner-title {
  display: inline-block; max-width: 100%;
  padding-right: .1em;
  border-right: .07em solid currentColor;
  clip-path: inset(0 100% 0 0);
  animation:
    ad-type-reveal calc(var(--ad-t, 1s) * .085 * var(--n, 20)) steps(var(--n, 20), end) .15s forwards,
    ad-type-caret 1.05s steps(1, end) calc(var(--ad-t, 1s) * .085 * var(--n, 20) + .15s) infinite;
}
.ad-tanim-type .ad-banner-title::after { display: none; }
@keyframes ad-type-reveal { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes ad-type-caret { 0%, 55% { border-right-color: currentColor; } 56%, 100% { border-right-color: transparent; } }
.ad-tanim-type .ad-banner-sub {
  animation: ad-fade-in calc(var(--ad-t, 1s) * .6) ease-out calc(var(--ad-t, 1s) * .085 * var(--n, 20) + .2s) both;
}

/* СТЕКЛО - заново. Вместо широкой мутной полосы - узкая стеклянная пластина с бликом по
   краю: она проходит поперёк один раз за цикл, за ней лёгкое искажение, и долгая пауза.
   Текст размывается только при первом появлении, дальше читается всегда. */
.ad-tanim-glass .ad-banner-body::after {
  width: 18%; left: 0;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .06) 35%,
              rgba(255, 255, 255, .22) 50%, rgba(255, 255, 255, .06) 65%, rgba(255, 255, 255, 0) 100%);
  -webkit-backdrop-filter: blur(2.5px) saturate(1.25) brightness(1.08);
          backdrop-filter: blur(2.5px) saturate(1.25) brightness(1.08);
  border-left: 1px solid rgba(255, 255, 255, .45);
  border-right: 1px solid rgba(255, 255, 255, .18);
  transform: translateX(-120%) skewX(-12deg);
  animation: ad-glass-pane calc(var(--ad-t, 1s) * 6) cubic-bezier(.55, .05, .3, 1) .3s infinite;
}
@keyframes ad-glass-pane {
  0%        { transform: translateX(-120%) skewX(-12deg); }
  40%       { transform: translateX(620%) skewX(-12deg); }
  40.1%, 100% { transform: translateX(620%) skewX(-12deg); }
}
.ad-tanim-glass .ad-banner-title,
.ad-tanim-glass .ad-banner-sub {
  animation: ad-glass-text-in calc(var(--ad-t, 1s) * 1.2) cubic-bezier(.2, .7, .3, 1) both;
}
.ad-tanim-glass .ad-banner-sub { animation-delay: calc(var(--ad-t, 1s) * .25); }
@keyframes ad-glass-text-in {
  from { filter: blur(5px); opacity: 0; transform: translateY(4px); }
  to   { filter: none; opacity: 1; transform: none; }
}

/* Побуквенные входы - мягче: без рывка в первые кадры, чуть больше шаг между буквами. */
.ad-tanim-letters .ad-banner-title span,
.ad-tanim-flip .ad-banner-title span,
.ad-tanim-wave .ad-banner-title span,
.ad-tanim-assemble .ad-banner-title span,
.ad-tanim-magnet .ad-banner-title span {
  animation-timing-function: cubic-bezier(.22, .8, .28, 1);
}

@media (prefers-reduced-motion: reduce) {
  .ad-tanim-type .ad-banner-title { clip-path: none; animation: none; border-right-color: transparent; }
  .ad-tanim-glass .ad-banner-body::after,
  .ad-tanim-glass .ad-banner-title, .ad-tanim-glass .ad-banner-sub { animation: none; }
}

/* ПАРАЛЛАКС - исправление. Текстовый слой двигать нельзя: вместе с ним ездило и
   затемнение под текстом, и по краям баннера открывались светлые полосы фото. Теперь
   объём делает только снимок: он медленно наклоняется в перспективе, как окно, мимо
   которого проходишь, - текст стоит, фото «уходит вглубь». */
.ad-banim-parallax .ad-banner-body { animation: none; }
.ad-banim-parallax .ad-banner-bg {
  animation: ad-parallax-tilt calc(var(--ad-t, 1s) * 11) ease-in-out infinite;
  transform-origin: 50% 50%;
}
@keyframes ad-parallax-tilt {
  0%, 100% { transform: perspective(520px) scale(calc(var(--ad-bg-scale, 1) * 1.2)) rotateY(-4deg) rotateX(1.5deg) translateX(-1.5%); }
  50%      { transform: perspective(520px) scale(calc(var(--ad-bg-scale, 1) * 1.2)) rotateY(4deg) rotateX(-1.5deg) translateX(1.5%); }
}

/* ═══ Движение фона: своя скорость (--ad-bt) и новый набор (2026-09-17) ════════ */
.ad-banim-zoom .ad-banner-bg     { animation-duration: calc(var(--ad-bt, 1s) * 12); }
.ad-banim-pan .ad-banner-bg      { animation-duration: calc(var(--ad-bt, 1s) * 13); }
.ad-banim-parallax .ad-banner-bg { animation-duration: calc(var(--ad-bt, 1s) * 11); }
.ad-banim-drift .ad-banner-bg    { animation-duration: calc(var(--ad-bt, 1s) * 18); }
.ad-banim-shine::before          { animation-duration: calc(var(--ad-bt, 1s) * 6); }
.ad-banim-flare::before          { animation-duration: calc(var(--ad-bt, 1s) * 9); }
.ad-banim-scan::before           { animation-duration: calc(var(--ad-bt, 1s) * 7); }
.ad-banim-beam::before           { animation-duration: calc(var(--ad-bt, 1s) * 7); }

/* БОКЕ. Мягкие круги расфокусированного света медленно всплывают по снимку, как огни
   за витриной ночью. Три слоя кругов разного размера движутся с разной скоростью -
   поэтому глубина читается сама. Смешивание «экран»: огни светят, а не закрашивают. */
.ad-banim-bokeh::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background-image:
    radial-gradient(circle, rgba(255, 236, 190, .55) 0 18%, rgba(255, 236, 190, 0) 60%),
    radial-gradient(circle, rgba(190, 225, 255, .45) 0 14%, rgba(190, 225, 255, 0) 58%),
    radial-gradient(circle, rgba(255, 255, 255, .35) 0 10%, rgba(255, 255, 255, 0) 55%);
  background-size: 34% 110%, 22% 70%, 14% 46%;
  background-repeat: repeat;
  mix-blend-mode: screen;
  filter: blur(1.2px);
  animation: ad-bokeh calc(var(--ad-bt, 1s) * 14) linear infinite;
}
@keyframes ad-bokeh {
  from { background-position: 8% 120%, 62% 140%, 30% 160%; opacity: .85; }
  50%  { opacity: 1; }
  to   { background-position: 20% -110%, 50% -210%, 44% -330%; opacity: .85; }
}

/* ЖАЛЮЗИ. Тени от горизонтальных планок медленно проходят по снимку - как солнце
   сквозь жалюзи в офисе. Мягкие края полос, смешивание «умножение»: фото темнеет
   полосами, а не закрашивается чёрным. */
.ad-banim-blinds::before {
  content: ""; position: absolute; inset: -20% -10%; z-index: 1; pointer-events: none;
  background: repeating-linear-gradient(172deg,
    rgba(0, 0, 0, 0) 0px, rgba(0, 0, 0, 0) 14px,
    rgba(10, 14, 20, .34) 20px, rgba(10, 14, 20, .34) 30px,
    rgba(0, 0, 0, 0) 36px);
  mix-blend-mode: multiply;
  filter: blur(1.6px);
  animation: ad-blinds calc(var(--ad-bt, 1s) * 10) ease-in-out infinite alternate;
}
@keyframes ad-blinds {
  from { transform: translateY(-18px) skewY(-1deg); }
  to   { transform: translateY(18px) skewY(1deg); }
}

/* ПРОЖЕКТОР. Весь снимок приглушён, а по нему медленно ездит круг света - будто
   прожектор выхватывает витрину. Край круга мягкий, текст под затемнением читается. */
.ad-banim-spotlight::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(circle at center, rgba(0, 0, 0, 0) 0 11%, rgba(4, 8, 14, .62) 26%);
  background-size: 300% 300%;
  animation: ad-spot calc(var(--ad-bt, 1s) * 9) ease-in-out infinite;
}
@keyframes ad-spot {
  0%, 100% { background-position: 85% 42%; }
  33%      { background-position: 42% 62%; }
  66%      { background-position: 18% 38%; }
}

/* ЦВЕТОКОР. Снимок медленно переходит между двумя цветовыми решениями - тёплым
   «закатным» и холодным «киношным», как грейдинг в рекламном ролике. Размытие из
   настроек сохраняется: оно входит в тот же фильтр. */
.ad-banim-tone .ad-banner-bg {
  animation: ad-tone calc(var(--ad-bt, 1s) * 10) ease-in-out infinite alternate;
}
@keyframes ad-tone {
  from { filter: blur(calc(var(--ad-blur, 0) * 1px)) sepia(.28) saturate(1.35) hue-rotate(-12deg) contrast(1.05); }
  to   { filter: blur(calc(var(--ad-blur, 0) * 1px)) saturate(1.15) hue-rotate(14deg) contrast(1.12) brightness(.96); }
}

/* ЖИДКОЕ СТЕКЛО - заново, в духе Liquid Glass. По строке медленно плывёт стеклянная
   капсула: под ней текст и фото чуть увеличены и ярче, по краю - светлый кант и тонкий
   блик сверху, снизу - мягкая тень. Узнаётся сразу и не мешает читать. */
.ad-tanim-glass .ad-banner-body { position: relative; overflow: hidden; }
.ad-tanim-glass .ad-banner-body::after {
  content: ""; position: absolute; pointer-events: none;
  top: auto; bottom: calc(18px * var(--ad-pv-scale, 1) + .55em);
  left: 0; width: 4.2em; height: 1.5em;
  font-size: var(--ad-title-size, 26px);
  border-radius: 999px; border: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, .28), rgba(255, 255, 255, .06) 45%, rgba(255, 255, 255, .12));
  -webkit-backdrop-filter: blur(1.2px) saturate(1.8) brightness(1.18) contrast(1.08);
          backdrop-filter: blur(1.2px) saturate(1.8) brightness(1.18) contrast(1.08);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .75),
    inset 0 -1px 0 rgba(255, 255, 255, .22),
    inset 0 0 0 1px rgba(255, 255, 255, .28),
    0 6px 16px rgba(0, 0, 0, .28);
  transform: translateX(-110%);
  animation: ad-liquid calc(var(--ad-t, 1s) * 7) cubic-bezier(.45, .05, .35, 1) .4s infinite;
}
@keyframes ad-liquid {
  0%   { transform: translateX(-110%) scaleX(.9); }
  10%  { transform: translateX(10%) scaleX(1.06); }
  45%  { transform: translateX(calc(100cqw - 20%)) scaleX(1); }
  55%  { transform: translateX(calc(100cqw - 20%)) scaleX(.94); }
  90%  { transform: translateX(10%) scaleX(1.04); }
  100% { transform: translateX(-110%) scaleX(.9); }
}
.ad-tanim-glass .ad-banner-body { container-type: inline-size; }
.ad-tanim-glass .ad-banner-title,
.ad-tanim-glass .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .9) cubic-bezier(.2, .7, .3, 1) both; }
.ad-tanim-glass .ad-banner-sub { animation-delay: calc(var(--ad-t, 1s) * .2); }

@media (prefers-reduced-motion: reduce) {
  .ad-banim-bokeh::before, .ad-banim-blinds::before, .ad-banim-spotlight::before,
  .ad-banim-tone .ad-banner-bg, .ad-tanim-glass .ad-banner-body::after { animation: none; }
}

/* Жидкое стекло: капсула живёт ВНУТРИ строки заголовка - едет ровно по буквам, а не
   висит отдельной кнопкой над текстом. Высота и ширина в em - от размера заголовка. */
.ad-tanim-glass .ad-banner-body::after { content: none; display: none; animation: none; }
.ad-tanim-glass .ad-banner-title { position: relative; overflow: hidden; padding: .08em 0; }
.ad-tanim-glass .ad-banner-title::after {
  content: ""; position: absolute; pointer-events: none;
  top: .02em; bottom: .02em; left: -3.6em; width: 3.2em;
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(255, 255, 255, .30), rgba(255, 255, 255, .04) 50%, rgba(255, 255, 255, .14));
  -webkit-backdrop-filter: blur(.6px) saturate(1.9) brightness(1.25) contrast(1.1);
          backdrop-filter: blur(.6px) saturate(1.9) brightness(1.25) contrast(1.1);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .8),
    inset 0 -1px 0 rgba(255, 255, 255, .25),
    inset 0 0 0 1px rgba(255, 255, 255, .3),
    0 3px 10px rgba(0, 0, 0, .3);
  animation: ad-liquid-run calc(var(--ad-t, 1s) * 6) cubic-bezier(.5, .05, .3, 1) .5s infinite;
}
@keyframes ad-liquid-run {
  0%        { left: -3.6em; transform: scaleX(.85); }
  12%       { transform: scaleX(1.08); }
  50%       { left: calc(100% + .4em); transform: scaleX(.95); }
  50.1%, 100% { left: calc(100% + .4em); transform: scaleX(.85); }
}
/* Боке и жалюзи - заметнее: на пёстром фото прежние значения терялись. */
.ad-banim-bokeh::before { opacity: 1; filter: blur(.8px) brightness(1.15); }
.ad-banim-blinds::before {
  background: repeating-linear-gradient(172deg,
    rgba(0, 0, 0, 0) 0px, rgba(0, 0, 0, 0) 12px,
    rgba(8, 12, 18, .5) 19px, rgba(8, 12, 18, .5) 29px, rgba(0, 0, 0, 0) 36px);
}
@media (prefers-reduced-motion: reduce) { .ad-tanim-glass .ad-banner-title::after { animation: none; display: none; } }

/* ═══ «Заказать рекламу» - выпадающая карточка ════════════════════════════════
   Карточка с рупором, заголовком, ценой и стрелкой. Вырастает из кнопки с пружиной,
   у верхнего края - хвостик к кнопке; внутри по карточке один раз пробегает блик. */
.ad-banner-order {
  right: 4px; top: calc(100% - 2px); bottom: auto; z-index: 8;
  height: auto; padding: 10px 12px 10px 10px; border-radius: 16px;
  display: flex; align-items: center; gap: 10px; min-width: 238px;
  background: linear-gradient(135deg, #ffffff 0%, #f1faf6 100%);
  color: #0f172a; text-align: left;
  box-shadow: 0 14px 34px rgba(15, 23, 42, .28), 0 2px 6px rgba(15, 23, 42, .12);
  font: inherit; line-height: 1.2; overflow: visible;
  opacity: 0; transform-origin: calc(100% - 26px) 0;
  transform: translateY(-10px) scale(.4) rotate(-4deg);
  transition: opacity .2s ease, transform .5s cubic-bezier(.2, 1.5, .35, 1);
}
.ad-banner-order.is-open { opacity: 1; transform: none; pointer-events: auto; }
.ad-banner-order.is-open:active { transform: scale(.96); }
/* хвостик к кнопке */
.ad-banner-order::before {
  content: ""; position: absolute; top: -6px; right: 22px; width: 12px; height: 12px;
  background: #ffffff; transform: rotate(45deg); border-radius: 2px;
}
/* блик пробегает при открытии */
.ad-banner-order::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(110deg, transparent 30%, rgba(255, 255, 255, .9) 48%, transparent 66%) -150% 0 / 60% 100% no-repeat;
}
.ad-banner-order.is-open::after { animation: aboShine .9s .25s ease both; }
@keyframes aboShine { to { background-position: 250% 0; } }
.abo-ico {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, #16a34a, #0E6B5B); color: #fff;
  box-shadow: 0 4px 10px rgba(14, 107, 91, .35);
}
.abo-ico .line-ico { width: 20px; height: 20px; }
.ad-banner-order.is-open .abo-ico { animation: aboIco .7s .15s cubic-bezier(.3, 1.6, .5, 1) both; }
@keyframes aboIco { 0% { transform: scale(.3) rotate(-30deg); } 60% { transform: scale(1.12) rotate(8deg); } 100% { transform: none; } }
.abo-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.abo-text b { font-size: 15.5px; font-weight: 700; white-space: nowrap; }
.abo-text small { font-size: 12.5px; color: #64748b; white-space: nowrap; }
.abo-go { flex: 0 0 auto; color: #0E6B5B; display: flex; }
.abo-go .line-ico { width: 20px; height: 20px; stroke-width: 2.4; }
.ad-banner-order.is-open .abo-go { animation: aboGo 1.4s .6s ease-in-out infinite; }
@keyframes aboGo { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(4px); } }
/* кнопка-квадрат чуть заметнее и с подсказкой-пульсом, пока карточка закрыта */
.ad-banner-more { width: 30px; height: 30px; border-radius: 9px; opacity: .7; }
.ad-banner-more:not(.is-open)::after {
  content: ""; position: absolute; inset: -1px; border-radius: inherit; pointer-events: none;
  box-shadow: 0 0 0 0 rgba(255, 255, 255, .45); animation: aboPing 2.8s ease-out 1.5s infinite;
}
@keyframes aboPing { 0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, .45); } 70%, 100% { box-shadow: 0 0 0 9px rgba(255, 255, 255, 0); } }
@media (prefers-reduced-motion: reduce) {
  .ad-banner-order, .ad-banner-order.is-open::after, .abo-ico, .abo-go, .ad-banner-more::after { animation: none !important; transition: opacity .2s; }
}
.ad-banner-order::before { right: 13px; }


/* МАРКЕР. За заголовком слева направо прорисовывается полупрозрачная полоса
   текстовыделителя - как будто главное выделили маркером. Полоса по нижней половине
   букв, чуть с наклоном краёв; текст виден сразу, выделение - дорисовывается. */
.ad-tanim-marker .ad-banner-title {
  display: inline-block; max-width: 100%;
  padding: 0 .14em; margin-left: -.14em;
  background-image: linear-gradient(100deg, rgba(250, 204, 21, 0) 0, rgba(250, 204, 21, .62) .3em,
                    rgba(250, 204, 21, .55) calc(100% - .3em), rgba(250, 204, 21, 0) 100%);
  background-repeat: no-repeat;
  background-position: 0 72%;
  background-size: 0% 46%;
  animation: ad-marker calc(var(--ad-t, 1s) * 1) cubic-bezier(.65, 0, .35, 1) calc(var(--ad-t, 1s) * .25) forwards;
}
@keyframes ad-marker { to { background-size: 100% 46%; } }
.ad-tanim-marker .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .7) ease-out calc(var(--ad-t, 1s) * .9) both; }

/* БАРАБАН. Каждая буква прокручивается сверху вниз, как символ на барабане
   слот-машины, в движении размыта по вертикали и по очереди встаёт на место с
   коротким докатом. Строка заголовка - «окно» барабана: всё, что выше и ниже, скрыто. */
.ad-tanim-drum .ad-banner-title { overflow: hidden; }
.ad-tanim-drum .ad-banner-title span {
  display: inline-block; white-space: pre;
  animation: ad-drum calc(var(--ad-t, 1s) * .9) cubic-bezier(.3, .6, .35, 1) both;
  animation-delay: calc(var(--i, 0) * var(--ad-t, 1s) * .045);
}
@keyframes ad-drum {
  0%   { transform: translateY(-260%); filter: blur(3px); opacity: .2; }
  30%  { transform: translateY(160%);  filter: blur(3px); opacity: .8; }
  31%  { transform: translateY(-160%); }
  62%  { transform: translateY(40%);   filter: blur(1.5px); opacity: 1; }
  80%  { transform: translateY(-12%);  filter: blur(0); }
  100% { transform: none; filter: none; opacity: 1; }
}
.ad-tanim-drum .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .7) ease-out calc(var(--ad-t, 1s) * .9) both; }

@media (prefers-reduced-motion: reduce) {
  .ad-tanim-marker .ad-banner-title { animation: none; background-size: 100% 46%; }
  .ad-tanim-drum .ad-banner-title span { animation: none; }
}
/* Маркер: полоса шире - на нижние 60% букв, иначе читалась как подчёркивание. */
.ad-tanim-marker .ad-banner-title { padding-top: .04em; padding-bottom: .06em; background-position: 0 100%; }
@keyframes ad-marker { from { background-size: 0% 62%; } to { background-size: 100% 62%; } }
.ad-tanim-marker .ad-banner-title { background-size: 0% 62%; }


/* РАЗРЕЗ. Заголовок разрезан по горизонтали: верхняя половина букв влетает слева,
   нижняя - справа, они проскальзывают друг мимо друга и точно смыкаются в строку.
   В момент смыкания по линии разреза пробегает тонкая светлая черта. */
.ad-tanim-split .ad-banner-title {
  position: relative; display: inline-block; max-width: 100%;
  color: transparent !important; -webkit-text-fill-color: transparent;
  animation: ad-split-solid 0s linear calc(var(--ad-t, 1s) * 1.05) forwards;
}
.ad-tanim-split .ad-banner-title::before,
.ad-tanim-split .ad-banner-title::after {
  content: attr(data-text);
  position: absolute; left: 0; top: 0; white-space: nowrap;
  color: #fff; -webkit-text-fill-color: #fff;
  pointer-events: none;
}
.ad-tanim-split .ad-banner-title::before {
  clip-path: inset(0 0 50% 0);
  animation: ad-split-top calc(var(--ad-t, 1s) * 1.05) cubic-bezier(.7, 0, .2, 1) both;
}
.ad-tanim-split .ad-banner-title::after {
  clip-path: inset(50% 0 0 0);
  animation: ad-split-bottom calc(var(--ad-t, 1s) * 1.05) cubic-bezier(.7, 0, .2, 1) both;
}
@keyframes ad-split-top {
  0%   { transform: translateX(-120%); opacity: 0; }
  15%  { opacity: 1; }
  82%  { transform: translateX(3%); }
  100% { transform: none; opacity: 1; }
}
@keyframes ad-split-bottom {
  0%   { transform: translateX(120%); opacity: 0; }
  15%  { opacity: 1; }
  82%  { transform: translateX(-3%); }
  100% { transform: none; opacity: 1; }
}
/* после смыкания строка становится обычным текстом, половинки убираются */
@keyframes ad-split-solid { to { color: inherit; -webkit-text-fill-color: currentColor; } }
.ad-tanim-split .ad-banner-body { position: relative; }
.ad-tanim-split .ad-banner-body::before {
  content: ""; position: absolute; left: 0; right: 0; height: 1px; pointer-events: none;
  top: calc(18px * var(--ad-pv-scale, 1) + .52em);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .9), transparent);
  opacity: 0;
  animation: ad-split-line calc(var(--ad-t, 1s) * .5) ease-out calc(var(--ad-t, 1s) * .8) both;
}
@keyframes ad-split-line { 0% { opacity: 0; transform: scaleX(.2); } 40% { opacity: 1; } 100% { opacity: 0; transform: scaleX(1); } }
.ad-tanim-split .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .6) ease-out calc(var(--ad-t, 1s) * 1) both; }
@media (prefers-reduced-motion: reduce) {
  .ad-tanim-split .ad-banner-title { color: inherit !important; -webkit-text-fill-color: currentColor; animation: none; }
  .ad-tanim-split .ad-banner-title::before, .ad-tanim-split .ad-banner-title::after,
  .ad-tanim-split .ad-banner-body::before { display: none; }
}
/* Разрез: пока половинки летят, у самой строки нет ни цвета, ни тени - иначе под ними
   заранее проступала тень готового текста. В момент смыкания всё возвращается разом. */
.ad-tanim-split .ad-banner-title {
  color: unset !important;
  animation: ad-split-solid2 1ms steps(1, end) calc(var(--ad-t, 1s) * 1.05) both;
}
@keyframes ad-split-solid2 {
  from { color: transparent; -webkit-text-fill-color: transparent; text-shadow: none; -webkit-text-stroke-color: transparent; }
  to   { color: #fff; -webkit-text-fill-color: #fff; text-shadow: var(--ad-shadow, 0 1px 2px rgba(0,0,0,.6)); }
}


/* ═══ Фон: только настоящее движение снимка (2026-09-17) ═══════════════════════ */

/* КАЧАНИЕ. Снимок мягко качается, как вывеска на лёгком ветру: ось вверху, размах
   небольшой, с замедлением в крайних точках. Запас масштаба закрывает углы. */
.ad-banim-sway .ad-banner-bg {
  transform-origin: 50% -30%;
  animation: ad-sway2 calc(var(--ad-bt, 1s) * 5) cubic-bezier(.45, 0, .55, 1) infinite alternate;
}
@keyframes ad-sway2 {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) rotate(-1.6deg) translateX(-1.5%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) rotate(1.6deg) translateX(1.5%); }
}

/* ВОДА. Снимок колышется, как отражение в воде: SVG-фильтр смещает пиксели по медленно
   меняющейся волне (#adWaterFx в index.html), а сам кадр чуть покачивается вверх-вниз. */
.ad-banim-water .ad-banner-bg {
  filter: url(#adWaterFx) blur(calc(var(--ad-blur, 0) * 1px));
  animation: ad-water-bob calc(var(--ad-bt, 1s) * 6) ease-in-out infinite alternate;
}
@keyframes ad-water-bob {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)) translateY(-1.2%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)) translateY(1.2%); }
}

/* ФЛАГ. Снимок идёт волной, как ткань флага: край то уходит вглубь, то возвращается,
   кадр слегка изгибается. Перспектива даёт объём, без неё это был бы плоский перекос. */
.ad-banim-flag .ad-banner-bg {
  transform-origin: 0% 50%;
  animation: ad-flag calc(var(--ad-bt, 1s) * 4) ease-in-out infinite;
}
@keyframes ad-flag {
  0%, 100% { transform: perspective(500px) scale(calc(var(--ad-bg-scale, 1) * 1.18)) rotateY(0deg) skewY(0deg); }
  25%      { transform: perspective(500px) scale(calc(var(--ad-bg-scale, 1) * 1.18)) rotateY(-5deg) skewY(1.2deg); }
  50%      { transform: perspective(500px) scale(calc(var(--ad-bg-scale, 1) * 1.18)) rotateY(2deg) skewY(-.6deg); }
  75%      { transform: perspective(500px) scale(calc(var(--ad-bg-scale, 1) * 1.18)) rotateY(4deg) skewY(-1.2deg); }
}

/* ОРБИТА. Камера плавно облетает снимок по кругу: кадр непрерывно смещается по
   окружности с лёгким приближением на дальней точке - без остановок и разворотов. */
.ad-banim-orbit .ad-banner-bg {
  animation: ad-orbit calc(var(--ad-bt, 1s) * 14) linear infinite;
}
@keyframes ad-orbit {
  0%    { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) translate(3%, 0); }
  12.5% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translate(2.1%, 1.5%); }
  25%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.2)) translate(0, 2.2%); }
  37.5% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translate(-2.1%, 1.5%); }
  50%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) translate(-3%, 0); }
  62.5% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translate(-2.1%, -1.5%); }
  75%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.2)) translate(0, -2.2%); }
  87.5% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translate(2.1%, -1.5%); }
  100%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) translate(3%, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .ad-banim-sway .ad-banner-bg, .ad-banim-water .ad-banner-bg,
  .ad-banim-flag .ad-banner-bg, .ad-banim-orbit .ad-banner-bg { animation: none; }
}

/* Качание: запас масштаба больше и ось по центру - в крайней точке не открывался угол. */
.ad-banim-sway .ad-banner-bg { transform-origin: 50% 50%; }
@keyframes ad-sway2 {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22)) rotate(-1.6deg) translateX(-1.2%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22)) rotate(1.6deg) translateX(1.2%); }
}

/* Разрез: светлая черта по линии смыкания убрана (Кирилл 2026-09-17) - лишняя. */
.ad-tanim-split .ad-banner-body::before { content: none; display: none; animation: none; }

/* Вертикальная зелёная полоса у левого края баннера убрана (Кирилл 2026-09-17):
   неподвижная, съедала ширину и спорила с фото. */
.ad-banner::after { content: none !important; display: none !important; animation: none !important; }


/* ═══ Ещё движения букв (2026-09-17). Все повторяются циклом из скрипта. ═══════ */

/* ПО СЛОВАМ. Слова по очереди поднимаются из-за невидимой линии строки - приём из
   рекламных роликов. Строка - окно: пока слово ниже линии, его не видно. */
.ad-tanim-words .ad-banner-title { overflow: hidden; }
.ad-tanim-words .ad-banner-title span {
  display: inline-block; white-space: pre;
  animation: ad-words calc(var(--ad-t, 1s) * .75) cubic-bezier(.16, 1, .3, 1) both;
  animation-delay: calc(var(--w, 0) * var(--ad-t, 1s) * .18);
}
@keyframes ad-words { from { transform: translateY(110%); opacity: 0; } to { transform: none; opacity: 1; } }
.ad-tanim-words .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .6) ease-out calc(var(--ad-t, 1s) * .6) both; }

/* СТЯЖКА. Заголовок появляется с огромной разрядкой и плавно стягивается в плотное
   слово - как титр в трейлере. */
.ad-tanim-tighten .ad-banner-title {
  animation: ad-tighten calc(var(--ad-t, 1s) * 1.3) cubic-bezier(.16, 1, .3, 1) both;
}
@keyframes ad-tighten {
  from { letter-spacing: .55em; opacity: 0; }
  30%  { opacity: 1; }
  to   { letter-spacing: var(--ad-track, 0em); opacity: 1; }
}
.ad-tanim-tighten .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .6) ease-out calc(var(--ad-t, 1s) * .8) both; }

/* ПРЫЖОК. Заголовок падает сверху и дважды отскакивает, как мяч, прежде чем встать. */
.ad-tanim-bounce .ad-banner-title {
  animation: ad-bounce calc(var(--ad-t, 1s) * 1.1) linear both;
}
@keyframes ad-bounce {
  0%   { transform: translateY(-140%); opacity: 0; animation-timing-function: cubic-bezier(.5, 0, 1, 1); }
  35%  { transform: none; opacity: 1; animation-timing-function: cubic-bezier(0, 0, .5, 1); }
  55%  { transform: translateY(-22%); animation-timing-function: cubic-bezier(.5, 0, 1, 1); }
  72%  { transform: none; animation-timing-function: cubic-bezier(0, 0, .5, 1); }
  85%  { transform: translateY(-7%); animation-timing-function: cubic-bezier(.5, 0, 1, 1); }
  100% { transform: none; opacity: 1; }
}
.ad-tanim-bounce .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .5) ease-out calc(var(--ad-t, 1s) * .7) both; }

/* УДАР. Заголовок влетает на зрителя из огромного размера и «впечатывается» в баннер
   с коротким встряхиванием. Точка роста - левый край: текст прижат влево. */
.ad-tanim-zoomin .ad-banner-title {
  transform-origin: 0% 60%;
  animation: ad-zoomin calc(var(--ad-t, 1s) * .8) cubic-bezier(.2, .9, .3, 1) both;
}
@keyframes ad-zoomin {
  0%   { transform: scale(2.6); opacity: 0; }
  55%  { transform: scale(.96); opacity: 1; }
  70%  { transform: scale(1.02) translateX(-1px); }
  85%  { transform: scale(1) translateX(1px); }
  100% { transform: none; opacity: 1; }
}
.ad-tanim-zoomin .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .5) ease-out calc(var(--ad-t, 1s) * .55) both; }

/* КОНТУР. Сначала появляются только контуры букв, затем они заливаются цветом -
   как надпись, которую сперва обвели, а потом закрасили. */
.ad-tanim-outline .ad-banner-title {
  animation: ad-outline calc(var(--ad-t, 1s) * 1.6) ease-in-out both;
}
@keyframes ad-outline {
  0%   { -webkit-text-fill-color: transparent; -webkit-text-stroke: 1px rgba(255, 255, 255, .0); opacity: 0; }
  20%  { -webkit-text-fill-color: transparent; -webkit-text-stroke: 1.2px rgba(255, 255, 255, .95); opacity: 1; }
  55%  { -webkit-text-fill-color: transparent; -webkit-text-stroke: 1.2px rgba(255, 255, 255, .95); }
  100% { -webkit-text-fill-color: #fff; -webkit-text-stroke: var(--ad-stroke, .6px) rgba(0, 0, 0, .55); opacity: 1; }
}
.ad-tanim-outline .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .6) ease-out calc(var(--ad-t, 1s) * 1.1) both; }

/* ТАБЛО. Строка откидывается вниз, как пластина механического табло в аэропорту:
   поворот вокруг верхнего края с перелётом и доводкой. */
.ad-tanim-swing .ad-banner-body { perspective: 480px; }
.ad-tanim-swing .ad-banner-title {
  transform-origin: 50% 0%;
  animation: ad-swing calc(var(--ad-t, 1s) * 1.1) cubic-bezier(.2, .8, .3, 1) both;
}
@keyframes ad-swing {
  0%   { transform: rotateX(-95deg); opacity: 0; }
  45%  { transform: rotateX(22deg); opacity: 1; }
  70%  { transform: rotateX(-9deg); }
  88%  { transform: rotateX(3deg); }
  100% { transform: none; opacity: 1; }
}
.ad-tanim-swing .ad-banner-sub { animation: ad-fade-in calc(var(--ad-t, 1s) * .5) ease-out calc(var(--ad-t, 1s) * .7) both; }

/* ═══ Ещё движения фона ═══ */
/* ПУЛЬС. Фото мягко «дышит»: приближается и отдаляется, как сердцебиение в замедлении. */
.ad-banim-breathe .ad-banner-bg { animation: ad-breathe2 calc(var(--ad-bt, 1s) * 3.2) ease-in-out infinite; }
@keyframes ad-breathe2 {
  0%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.04)); }
  45%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)); }
}
/* СПИРАЛЬ. Фото медленно вращается и одновременно приближается - затягивает внутрь. */
.ad-banim-spiral .ad-banner-bg { animation: ad-spiral calc(var(--ad-bt, 1s) * 16) ease-in-out infinite alternate; }
@keyframes ad-spiral {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.35)) rotate(-3deg); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.55)) rotate(3deg); }
}

@media (prefers-reduced-motion: reduce) {
  .ad-tanim-words .ad-banner-title span, .ad-tanim-tighten .ad-banner-title,
  .ad-tanim-bounce .ad-banner-title, .ad-tanim-zoomin .ad-banner-title,
  .ad-tanim-outline .ad-banner-title, .ad-tanim-swing .ad-banner-title,
  .ad-banim-breathe .ad-banner-bg, .ad-banim-spiral .ad-banner-bg { animation: none; }
}

/* «Заказать рекламу»: рупор без зелёной плашки - тонкий серый контур, не забивает текст. */
.abo-ico {
  width: 26px; height: 26px; border-radius: 0; background: none; box-shadow: none;
  color: #64748b;
}
.abo-ico .line-ico { width: 22px; height: 22px; stroke-width: 1.8; }
.ad-banner-order.is-open .abo-ico { animation: none; }
.ad-banner-order { padding-left: 14px; gap: 12px; }

/* УДАР - заново. Заголовок летит на зрителя огромным и размытым от скорости, врезается
   в баннер: сплющивается от удара, пружинит обратно, а в точке удара вспыхивает свет и
   подпись вздрагивает от толчка. Тайминги связаны: вспышка и толчок ровно в момент касания. */
.ad-tanim-zoomin .ad-banner-title {
  transform-origin: 18% 60%;
  animation: ad-impact calc(var(--ad-t, 1s) * 1.1) cubic-bezier(.55, 0, .4, 1) both;
}
@keyframes ad-impact {
  0%   { transform: scale(3.4) translateY(-8%); opacity: 0; filter: blur(8px); }
  38%  { transform: scale(1.02); opacity: 1; filter: blur(0); }
  46%  { transform: scale(1.16, .78); }
  56%  { transform: scale(.93, 1.08); }
  66%  { transform: scale(1.04, .97) translateX(-2px); }
  76%  { transform: scale(.99, 1.01) translateX(2px); }
  86%  { transform: translateX(-1px); }
  100% { transform: none; opacity: 1; filter: none; }
}
/* вспышка в точке удара */
.ad-tanim-zoomin::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: radial-gradient(ellipse 60% 90% at 22% 62%, rgba(255, 255, 255, .75), rgba(255, 255, 255, 0) 60%);
  opacity: 0;
  animation: ad-impact-flash calc(var(--ad-t, 1s) * 1.1) ease-out both;
}
@keyframes ad-impact-flash {
  0%, 36% { opacity: 0; }
  42%     { opacity: .9; }
  70%     { opacity: 0; }
  100%    { opacity: 0; }
}
/* подпись вздрагивает от толчка и только потом проявляется полностью */
.ad-tanim-zoomin .ad-banner-sub {
  animation: ad-impact-sub calc(var(--ad-t, 1s) * 1.1) ease-out both;
}
@keyframes ad-impact-sub {
  0%, 38% { opacity: 0; transform: translateY(6px); }
  46%     { opacity: .9; transform: translateY(-3px); }
  58%     { transform: translateY(2px); }
  70%     { transform: translateY(-1px); }
  100%    { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ad-tanim-zoomin .ad-banner-title, .ad-tanim-zoomin .ad-banner-sub, .ad-tanim-zoomin::before { animation: none; }
}


/* ═══ Движение фона, заход третий (2026-09-17) ════════════════════════════════
   Не наложения и не «эффекты», а работа камеры, как в кино и дорогой рекламе.
   Всё двигает только сам снимок; скорость - «Скорость фона» (--ad-bt). */

/* КИНОКАМЕРА (эффект Кена Бёрнса). Камера медленно наезжает и одновременно ведёт кадр
   от угла к углу по плавной траектории - так в документальном кино «оживляют» фото. */
.ad-banim-kenburns .ad-banner-bg {
  animation: ad-kenburns calc(var(--ad-bt, 1s) * 20) cubic-bezier(.45, .05, .55, .95) infinite;
}
@keyframes ad-kenburns {
  0%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translate(-3%, -2%); }
  25%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22)) translate(3%, -1%); }
  50%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)) translate(2%, 2.5%); }
  75%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.26)) translate(-3%, 1.5%); }
  100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translate(-3%, -2%); }
}

/* С РУК. Живая ручная камера: кадр едва заметно «плавает» по неровной траектории с
   микроповоротами - ни одного повторяющегося рывка, как у оператора в руках. */
.ad-banim-handheld .ad-banner-bg {
  animation: ad-handheld calc(var(--ad-bt, 1s) * 9) ease-in-out infinite;
}
@keyframes ad-handheld {
  0%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.1)) translate(0, 0) rotate(0deg); }
  13%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.1)) translate(-.9%, .6%) rotate(-.35deg); }
  27%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.11)) translate(.7%, 1.1%) rotate(.25deg); }
  41%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.1)) translate(1.2%, -.4%) rotate(.4deg); }
  58%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)) translate(-.4%, -1%) rotate(-.2deg); }
  72%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.1)) translate(-1.1%, .3%) rotate(-.45deg); }
  86%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.11)) translate(.5%, .8%) rotate(.15deg); }
  100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.1)) translate(0, 0) rotate(0deg); }
}

/* 3D-НАКЛОН. Снимок как карточка в руке: плавно поворачивается сразу по двум осям по
   траектории «восьмёркой» - то один край ближе, то другой. Объём без единого наложения. */
.ad-banim-tilt3d .ad-banner-bg {
  animation: ad-tilt3d calc(var(--ad-bt, 1s) * 12) ease-in-out infinite;
}
@keyframes ad-tilt3d {
  0%   { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.24)) rotateX(0deg)   rotateY(-6deg); }
  25%  { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.24)) rotateX(4deg)   rotateY(0deg); }
  50%  { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.24)) rotateX(0deg)   rotateY(6deg); }
  75%  { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.24)) rotateX(-4deg)  rotateY(0deg); }
  100% { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.24)) rotateX(0deg)   rotateY(-6deg); }
}

/* ВОДА - спокойнее: рябь медленнее, кадр плавно покачивается на волне. */
.ad-banim-water .ad-banner-bg {
  animation: ad-water-float calc(var(--ad-bt, 1s) * 7) ease-in-out infinite;
}
@keyframes ad-water-float {
  0%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)) translate(0, -1%) rotate(-.3deg); }
  50%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)) translate(0, 1%) rotate(.3deg); }
}

/* СРЕЗЫ. Снимок нарезан на три горизонтальные полосы, и каждая медленно скользит в свою
   сторону и возвращается - как «разрез» у заголовка, только на фото. Верх и низ -
   копии снимка (псевдоэлементы наследуют его фон), середина - сам снимок. */
.ad-banim-slices .ad-banner-bg {
  animation: ad-slice-mid calc(var(--ad-bt, 1s) * 6) cubic-bezier(.65, 0, .35, 1) infinite;
}
.ad-banim-slices .ad-banner-bg::before,
.ad-banim-slices .ad-banner-bg::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: inherit; background-size: cover; background-position: center;
}
.ad-banim-slices .ad-banner-bg::before {
  clip-path: inset(0 0 66% 0);
  animation: ad-slice-top calc(var(--ad-bt, 1s) * 6) cubic-bezier(.65, 0, .35, 1) infinite;
}
.ad-banim-slices .ad-banner-bg::after {
  clip-path: inset(66% 0 0 0);
  animation: ad-slice-bottom calc(var(--ad-bt, 1s) * 6) cubic-bezier(.65, 0, .35, 1) infinite;
}
@keyframes ad-slice-mid {
  0%, 20%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)) translateX(0); }
  45%, 70%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)) translateX(2.5%); }
}
@keyframes ad-slice-top {
  0%, 20%, 100% { transform: translateX(0); }
  45%, 70%      { transform: translateX(-9%); }
}
@keyframes ad-slice-bottom {
  0%, 20%, 100% { transform: translateX(0); }
  45%, 70%      { transform: translateX(-6%); }
}

/* ВЕРТИГО. Приём Хичкока: камера наезжает, а кадр одновременно «растягивается» в
   перспективе - фон будто уходит вглубь, пока объект стоит на месте. Туда и обратно. */
.ad-banim-vertigo .ad-banner-bg {
  transform-origin: 50% 55%;
  animation: ad-vertigo calc(var(--ad-bt, 1s) * 8) cubic-bezier(.45, 0, .55, 1) infinite alternate;
}
@keyframes ad-vertigo {
  from { transform: perspective(700px) scale(calc(var(--ad-bg-scale, 1) * 1.12)) translateZ(0); }
  to   { transform: perspective(700px) scale(calc(var(--ad-bg-scale, 1) * 1.02)) translateZ(90px); }
}

@media (prefers-reduced-motion: reduce) {
  .ad-banim-kenburns .ad-banner-bg, .ad-banim-handheld .ad-banner-bg, .ad-banim-tilt3d .ad-banner-bg,
  .ad-banim-slices .ad-banner-bg, .ad-banim-slices .ad-banner-bg::before, .ad-banim-slices .ad-banner-bg::after,
  .ad-banim-vertigo .ad-banner-bg { animation: none; }
}


/* ═══ Движение фона, набор из 12 (2026-09-17) ═════════════════════════════════
   Приёмы: камера (Кен Бёрнс, 3D, «с рук», бит, эхо-зум), SVG-искажения
   (feTurbulence + feDisplacementMap: вода, марево, ткань) и нарезка кадра на части
   (срезы, колонны, осколки, сбой) - копии снимка в псевдоэлементах наследуют его фон. */

/* общий слой-копия для нарезок */
.ad-banim-slices .ad-banner-bg::before, .ad-banim-slices .ad-banner-bg::after,
.ad-banim-columns .ad-banner-bg::before, .ad-banim-columns .ad-banner-bg::after,
.ad-banim-shards .ad-banner-bg::before, .ad-banim-shards .ad-banner-bg::after,
.ad-banim-echozoom .ad-banner-bg::before, .ad-banim-echozoom .ad-banner-bg::after,
.ad-banim-glitchbg .ad-banner-bg::before, .ad-banim-glitchbg .ad-banner-bg::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: inherit; background-size: cover; background-position: center;
}

/* КИНОКАМЕРА - заметнее: наезд глубже, путь по углам шире. */
@keyframes ad-kenburns {
  0%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)) translate(-4%, -3%); }
  25%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.32)) translate(4%, -2%); }
  50%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translate(3%, 3.5%); }
  75%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.38)) translate(-4%, 2%); }
  100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)) translate(-4%, -3%); }
}
.ad-banim-kenburns .ad-banner-bg { animation-duration: calc(var(--ad-bt, 1s) * 16); }

/* 3D-НАКЛОН - глубже. */
@keyframes ad-tilt3d {
  0%   { transform: perspective(360px) scale(calc(var(--ad-bg-scale, 1) * 1.3)) rotateX(0deg)  rotateY(-9deg); }
  25%  { transform: perspective(360px) scale(calc(var(--ad-bg-scale, 1) * 1.3)) rotateX(6deg)  rotateY(0deg); }
  50%  { transform: perspective(360px) scale(calc(var(--ad-bg-scale, 1) * 1.3)) rotateX(0deg)  rotateY(9deg); }
  75%  { transform: perspective(360px) scale(calc(var(--ad-bg-scale, 1) * 1.3)) rotateX(-6deg) rotateY(0deg); }
  100% { transform: perspective(360px) scale(calc(var(--ad-bg-scale, 1) * 1.3)) rotateX(0deg)  rotateY(-9deg); }
}
.ad-banim-tilt3d .ad-banner-bg { animation-duration: calc(var(--ad-bt, 1s) * 9); }

/* БИТ. Кадр «качает» в ритм: резкий толчок наезда - и медленный отход, два удара
   подряд и пауза, как бочка в треке. */
.ad-banim-beat .ad-banner-bg { animation: ad-beat calc(var(--ad-bt, 1s) * 2.4) ease-out infinite; }
@keyframes ad-beat {
  0%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)); animation-timing-function: cubic-bezier(.1, .9, .2, 1); }
  8%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.17)); animation-timing-function: cubic-bezier(.4, 0, .6, 1); }
  30%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.07)); animation-timing-function: cubic-bezier(.1, .9, .2, 1); }
  38%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)); animation-timing-function: cubic-bezier(.4, 0, .6, 1); }
  70%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)); }
}

/* ЭХО-ЗУМ. Из снимка раз за разом «вылетают» его полупрозрачные копии: растут и тают,
   как волна от удара или сверхсветовой прыжок. Сам кадр при этом стоит. */
.ad-banim-echozoom .ad-banner-bg { transform: scale(calc(var(--ad-bg-scale, 1) * 1.04)); }
.ad-banim-echozoom .ad-banner-bg::before { animation: ad-echo calc(var(--ad-bt, 1s) * 3.2) cubic-bezier(.2, .6, .3, 1) infinite; }
.ad-banim-echozoom .ad-banner-bg::after  { animation: ad-echo calc(var(--ad-bt, 1s) * 3.2) cubic-bezier(.2, .6, .3, 1) calc(var(--ad-bt, 1s) * 1.6) infinite; }
@keyframes ad-echo {
  0%   { transform: scale(1); opacity: .55; }
  100% { transform: scale(1.45); opacity: 0; }
}

/* ВОДА - течёт: SVG-фильтр #adWaterFx с «вращающимся» шумом. */
.ad-banim-water .ad-banner-bg { filter: url(#adWaterFx) blur(calc(var(--ad-blur, 0) * 1px)); }

/* МАРЕВО. Снимок дрожит мелкой рябью, как воздух над горячим асфальтом. */
.ad-banim-heat .ad-banner-bg {
  filter: url(#adHeatFx) blur(calc(var(--ad-blur, 0) * 1px));
  transform: scale(calc(var(--ad-bg-scale, 1) * 1.06));
  animation: ad-heat-rise calc(var(--ad-bt, 1s) * 6) ease-in-out infinite alternate;
}
@keyframes ad-heat-rise {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)) translateY(.8%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translateY(-.8%); }
}

/* ТКАНЬ. По снимку медленно проходят крупные складки, как по полотну на ветру. */
.ad-banim-fabric .ad-banner-bg {
  filter: url(#adFabricFx) blur(calc(var(--ad-blur, 0) * 1px));
  transform: scale(calc(var(--ad-bg-scale, 1) * 1.1));
}

/* СРЕЗЫ - смелее: полосы уходят дальше и в разные стороны. */
@keyframes ad-slice-top    { 0%, 15%, 100% { transform: translateX(0); } 40%, 70% { transform: translateX(-12%); } }
@keyframes ad-slice-bottom { 0%, 15%, 100% { transform: translateX(0); } 40%, 70% { transform: translateX(10%); } }
@keyframes ad-slice-mid {
  0%, 15%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translateX(0); }
  40%, 70%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translateX(4%); }
}

/* КОЛОННЫ. Кадр разделён на три вертикальные колонны, они по очереди уезжают вверх и
   вниз волной и возвращаются - как панели на электронном табло. */
.ad-banim-columns .ad-banner-bg { transform: scale(calc(var(--ad-bg-scale, 1) * 1.2)); }
.ad-banim-columns .ad-banner-bg::before {
  clip-path: inset(0 66.6% 0 0);
  animation: ad-col calc(var(--ad-bt, 1s) * 5) cubic-bezier(.65, 0, .35, 1) infinite;
}
.ad-banim-columns .ad-banner-bg::after {
  clip-path: inset(0 0 0 66.6%);
  animation: ad-col calc(var(--ad-bt, 1s) * 5) cubic-bezier(.65, 0, .35, 1) calc(var(--ad-bt, 1s) * .5) infinite reverse;
}
@keyframes ad-col {
  0%, 12%, 100% { transform: translateY(0); }
  35%, 60%      { transform: translateY(-11%); }
  80%           { transform: translateY(4%); }
}

/* ОСКОЛКИ. Два треугольных осколка снимка отрываются, чуть поворачиваются и
   сдвигаются - кадр будто трескается - и тут же собираются обратно. */
.ad-banim-shards .ad-banner-bg { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)); }
.ad-banim-shards .ad-banner-bg::before {
  clip-path: polygon(0 0, 58% 0, 36% 100%, 0 100%);
  animation: ad-shard-a calc(var(--ad-bt, 1s) * 4.5) cubic-bezier(.7, 0, .3, 1) infinite;
}
.ad-banim-shards .ad-banner-bg::after {
  clip-path: polygon(58% 0, 100% 0, 100% 100%, 36% 100%);
  animation: ad-shard-b calc(var(--ad-bt, 1s) * 4.5) cubic-bezier(.7, 0, .3, 1) infinite;
}
@keyframes ad-shard-a {
  0%, 20%, 100% { transform: none; }
  40%, 62%      { transform: translate(-3.5%, -2%) rotate(-2.5deg); }
}
@keyframes ad-shard-b {
  0%, 20%, 100% { transform: none; }
  40%, 62%      { transform: translate(3.5%, 2%) rotate(2deg); }
}

/* СБОЙ. Кадр ровный, но время от времени «ломается»: полосы дёргаются в стороны и
   кадр подпрыгивает на несколько кадров - как сбой видеосигнала - и снова чисто. */
.ad-banim-glitchbg .ad-banner-bg { animation: ad-gl-main calc(var(--ad-bt, 1s) * 3.5) steps(1, end) infinite; }
.ad-banim-glitchbg .ad-banner-bg::before {
  clip-path: inset(18% 0 58% 0);
  animation: ad-gl-a calc(var(--ad-bt, 1s) * 3.5) steps(1, end) infinite;
}
.ad-banim-glitchbg .ad-banner-bg::after {
  clip-path: inset(62% 0 12% 0);
  animation: ad-gl-b calc(var(--ad-bt, 1s) * 3.5) steps(1, end) infinite;
}
@keyframes ad-gl-main {
  0%, 70%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translate(0, 0); }
  72%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translate(-1.5%, .6%); }
  75%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translate(1.2%, -.8%); }
  78%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translate(0, 0); }
  86%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translate(.8%, 0); }
  88%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.08)) translate(0, 0); }
}
@keyframes ad-gl-a {
  0%, 70%, 78%, 100% { transform: none; }
  72%  { transform: translateX(8%); }
  75%  { transform: translateX(-6%); }
  86%  { transform: translateX(4%); }
  88%  { transform: none; }
}
@keyframes ad-gl-b {
  0%, 70%, 78%, 100% { transform: none; }
  73%  { transform: translateX(-9%); }
  76%  { transform: translateX(5%); }
  87%  { transform: translateX(-3%); }
  89%  { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ad-banim-beat .ad-banner-bg, .ad-banim-heat .ad-banner-bg,
  .ad-banim-echozoom .ad-banner-bg::before, .ad-banim-echozoom .ad-banner-bg::after,
  .ad-banim-columns .ad-banner-bg::before, .ad-banim-columns .ad-banner-bg::after,
  .ad-banim-shards .ad-banner-bg::before, .ad-banim-shards .ad-banner-bg::after,
  .ad-banim-glitchbg .ad-banner-bg, .ad-banim-glitchbg .ad-banner-bg::before, .ad-banim-glitchbg .ad-banner-bg::after { animation: none; }
}

/* Ткань и марево: больше запас по краям - искажение не должно затягивать пустоту. */
.ad-banim-fabric .ad-banner-bg { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22)); }
.ad-banim-water .ad-banner-bg { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)); }
@keyframes ad-heat-rise {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)) translateY(.8%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14)) translateY(-.8%); }
}
/* Искажения тянут пиксели из-за края слоя - там пусто. Поэтому сам слой снимка у воды,
   марева и ткани шире баннера на 10% с каждой стороны: пустой край уходит за рамку. */
.ad-banim-water .ad-banner-bg, .ad-banim-heat .ad-banner-bg, .ad-banim-fabric .ad-banner-bg {
  inset: -10%; border-radius: 0;
}
.ad-banim-fabric .ad-banner-bg { transform: scale(var(--ad-bg-scale, 1)); }
.ad-banim-water .ad-banner-bg { transform: scale(var(--ad-bg-scale, 1)); }
@keyframes ad-heat-rise {
  from { transform: scale(var(--ad-bg-scale, 1)) translateY(.8%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.02)) translateY(-.8%); }
}
.ad-banim-water .ad-banner-bg, .ad-banim-heat .ad-banner-bg, .ad-banim-fabric .ad-banner-bg { inset: -18%; }


/* ── Карточка поставщика: смена фото и места ─────────────────────────────── */
.sup-ava-btn { position: relative; border: none; padding: 0; cursor: pointer; font: inherit; }
.sup-ava-cam {
  position: absolute; right: -2px; bottom: -2px; width: 22px; height: 22px; border-radius: 50%;
  background: #fff; color: var(--fg); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
}
.sup-ava-cam .line-ico { width: 13px; height: 13px; }
.sup-ava-btn:active { transform: scale(.95); }
.sup-row-text { flex: 1 1 auto; min-width: 0; }
.sup-edit-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px;
  border: none; background: rgba(15, 107, 91, .08); color: var(--pine, #0E6B5B);
  border-radius: 999px; padding: 5px 10px; font: 600 12.5px/1 inherit; font-family: inherit; cursor: pointer;
}
.sup-edit-btn .line-ico { width: 13px; height: 13px; }
.sup-loc {
  margin: 12px 0 4px; padding: 16px; border-radius: 18px; background: var(--bg);
  box-shadow: 0 2px 8px rgba(20, 24, 28, .06), 0 10px 28px rgba(20, 24, 28, .07);
  animation: earnSubsIn .3s cubic-bezier(.16, 1, .3, 1) both;
}
.sup-loc-title { font-size: 16px; font-weight: 700; margin-bottom: 10px; }
.sup-loc-kind { display: flex; gap: 6px; margin-bottom: 10px; padding: 4px; border-radius: 12px; background: var(--card); }
.sup-kind { flex: 1; border: none; background: none; padding: 9px 8px; border-radius: 9px; font: 600 14px/1 inherit; font-family: inherit; color: var(--fg-dim); cursor: pointer; }
.sup-kind.is-on { background: var(--bg); color: var(--fg); box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }
.sup-in {
  display: block; width: 100%; box-sizing: border-box; margin-bottom: 8px;
  padding: 11px 12px; border-radius: 12px; border: 1px solid var(--border); background: var(--card);
  font: 15px/1.2 inherit; font-family: inherit; color: var(--fg);
}
.sup-loc-row { display: flex; gap: 8px; }
.sup-loc-row .sup-in { flex: 1; min-width: 0; }
.sup-loc-actions { display: flex; gap: 8px; margin-top: 6px; }
.sup-loc-actions .idea-btn { flex: 0 0 auto; width: auto; padding: 0 16px; }
.sup-loc-actions .submit-btn { flex: 1; margin: 0; }
#supLocHint { color: #dc2626; min-height: 0; }


/* ═══ Движение фона: необычное, но цельное (2026-09-17) ═══════════════════════
   Правила набора: снимок никогда не рвётся на части и не перекрашивается - только
   движение целого кадра (или его цельной копии). Скорость - «Скорость фона». */

/* ПОЛЁТ. Бесконечный полёт вперёд сквозь снимок: кадр непрерывно приближается, а его
   копия подхватывает движение с начала и плавно проявляется - шва и остановки нет. */
.ad-banim-flight .ad-banner-bg { animation: ad-flight calc(var(--ad-bt, 1s) * 7) linear infinite; }
.ad-banim-flight .ad-banner-bg::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: inherit; background-size: cover; background-position: center;
  animation: ad-flight-copy calc(var(--ad-bt, 1s) * 7) linear infinite;
}
@keyframes ad-flight {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.02)); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.7)); }
}
/* копия внутри увеличенного слоя: сначала прозрачна и «мала» (компенсирует рост слоя),
   к концу цикла совпадает с началом следующего круга */
@keyframes ad-flight-copy {
  0%   { transform: scale(.6); opacity: 0; }
  55%  { opacity: 0; }
  100% { transform: scale(.6); opacity: 1; }
}

/* ЛУПА. По снимку медленно ездит увеличительное стекло: внутри круга - тот же кадр,
   увеличенный вдвое ровно в точке линзы, с тонкой светлой оправой. */
.ad-banim-lens .ad-banner-bg::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: inherit; background-size: cover; background-position: center;
  clip-path: circle(20% at 25% 50%);
  transform-origin: 25% 50%;
  transform: scale(1.9);
  animation: ad-lens calc(var(--ad-bt, 1s) * 10) ease-in-out infinite;
}
@keyframes ad-lens {
  0%, 100% { clip-path: circle(11% at 22% 48%); transform-origin: 22% 48%; }
  33%      { clip-path: circle(11% at 58% 36%); transform-origin: 58% 36%; }
  66%      { clip-path: circle(11% at 80% 62%); transform-origin: 80% 62%; }
}
/* оправа линзы - отдельным кольцом, чтобы не увеличивалась вместе с кадром */
.ad-banim-lens::before {
  content: ""; position: absolute; z-index: 1; pointer-events: none;
  width: 22%; aspect-ratio: 1; left: 22%; top: 48%;
  border-radius: 50%; transform: translate(-50%, -50%);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .75), 0 6px 18px rgba(0, 0, 0, .35), inset 0 0 12px rgba(255, 255, 255, .25);
  animation: ad-lens-ring calc(var(--ad-bt, 1s) * 10) ease-in-out infinite;
}
@keyframes ad-lens-ring {
  0%, 100% { left: 22%; top: 48%; }
  33%      { left: 58%; top: 36%; }
  66%      { left: 80%; top: 62%; }
}

/* НЕВЕСОМОСТЬ. Кадр парит, как в космосе: медленно плывёт по сложной траектории,
   чуть вращается и приближается - ни одно движение не повторяется в такт. */
.ad-banim-zerog .ad-banner-bg { animation: ad-zerog calc(var(--ad-bt, 1s) * 18) ease-in-out infinite; }
@keyframes ad-zerog {
  0%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22)) translate(0, 0) rotate(0deg); }
  17%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.28)) translate(-3%, 2%) rotate(-2deg); }
  34%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.24)) translate(2%, 3%) rotate(1.5deg); }
  51%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.3)) translate(3.5%, -1.5%) rotate(2.5deg); }
  68%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.23)) translate(-1%, -3%) rotate(-1deg); }
  85%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.27)) translate(-3.5%, .5%) rotate(-2.5deg); }
  100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22)) translate(0, 0) rotate(0deg); }
}

/* ЖЕЛЕ. Кадр упруго «дышит» по-разному в ширину и в высоту - сжимается по одной оси
   и растягивается по другой, с затухающими колебаниями, как кусок желе после толчка. */
.ad-banim-jelly .ad-banner-bg { animation: ad-jelly calc(var(--ad-bt, 1s) * 3.6) ease-in-out infinite; }
@keyframes ad-jelly {
  0%, 60%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.14), calc(var(--ad-bg-scale, 1) * 1.14)); }
  10% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.22), calc(var(--ad-bg-scale, 1) * 1.08)); }
  20% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.09), calc(var(--ad-bg-scale, 1) * 1.21)); }
  30% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18), calc(var(--ad-bg-scale, 1) * 1.11)); }
  40% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12), calc(var(--ad-bg-scale, 1) * 1.17)); }
  50% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.15), calc(var(--ad-bg-scale, 1) * 1.13)); }
}

/* КАЧКА. Кадр как с палубы корабля: плавно поднимается и опускается на волне,
   кренится то на один борт, то на другой и слегка «кивает» вперёд. */
.ad-banim-boat .ad-banner-bg { animation: ad-boat calc(var(--ad-bt, 1s) * 6) ease-in-out infinite; }
@keyframes ad-boat {
  0%, 100% { transform: perspective(600px) scale(calc(var(--ad-bg-scale, 1) * 1.26)) translateY(0) rotateZ(-2.5deg) rotateX(0deg); }
  25%      { transform: perspective(600px) scale(calc(var(--ad-bg-scale, 1) * 1.26)) translateY(-3%) rotateZ(0deg) rotateX(4deg); }
  50%      { transform: perspective(600px) scale(calc(var(--ad-bg-scale, 1) * 1.26)) translateY(0) rotateZ(2.5deg) rotateX(0deg); }
  75%      { transform: perspective(600px) scale(calc(var(--ad-bg-scale, 1) * 1.26)) translateY(3%) rotateZ(0deg) rotateX(-4deg); }
}

/* МАЯТНИК. Кадр висит на невидимой оси сверху и качается в объёме: нижний край
   уходит вглубь то влево, то вправо, с замедлением в крайних точках. */
.ad-banim-pendulum .ad-banner-bg {
  transform-origin: 50% -60%;
  animation: ad-pendulum calc(var(--ad-bt, 1s) * 4.5) cubic-bezier(.45, 0, .55, 1) infinite alternate;
}
@keyframes ad-pendulum {
  from { transform: perspective(500px) scale(calc(var(--ad-bg-scale, 1) * 1.3)) rotateZ(-3deg) rotateY(-7deg); }
  to   { transform: perspective(500px) scale(calc(var(--ad-bg-scale, 1) * 1.3)) rotateZ(3deg) rotateY(7deg); }
}

/* ГИРОСКОП. Кадр непрерывно «оглядывается» по кругу в 3D - наклон бежит вокруг центра,
   как у гироскопа или телефона, который плавно вращают в руке. */
.ad-banim-gyro .ad-banner-bg { animation: ad-gyro calc(var(--ad-bt, 1s) * 8) linear infinite; }
@keyframes ad-gyro {
  0%    { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(6deg)  rotateY(0deg); }
  12.5% { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(4.2deg) rotateY(4.2deg); }
  25%   { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(0deg)  rotateY(6deg); }
  37.5% { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(-4.2deg) rotateY(4.2deg); }
  50%   { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(-6deg) rotateY(0deg); }
  62.5% { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(-4.2deg) rotateY(-4.2deg); }
  75%   { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(0deg)  rotateY(-6deg); }
  87.5% { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(4.2deg) rotateY(-4.2deg); }
  100%  { transform: perspective(420px) scale(calc(var(--ad-bg-scale, 1) * 1.32)) rotateX(6deg)  rotateY(0deg); }
}

/* ГИПНОЗ. Кадр медленно закручивается воронкой: приближается с поворотом, отходит с
   обратным поворотом - затягивает взгляд в центр баннера. */
.ad-banim-hypno .ad-banner-bg { animation: ad-hypno calc(var(--ad-bt, 1s) * 9) cubic-bezier(.45, 0, .55, 1) infinite alternate; }
@keyframes ad-hypno {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.45)) rotate(-7deg); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.95)) rotate(7deg); }
}

@media (prefers-reduced-motion: reduce) {
  .ad-banim-flight .ad-banner-bg, .ad-banim-flight .ad-banner-bg::before,
  .ad-banim-lens .ad-banner-bg::after, .ad-banim-lens::before,
  .ad-banim-zerog .ad-banner-bg, .ad-banim-jelly .ad-banner-bg, .ad-banim-boat .ad-banner-bg,
  .ad-banim-pendulum .ad-banner-bg, .ad-banim-gyro .ad-banner-bg, .ad-banim-hypno .ad-banner-bg { animation: none; }
}
/* Маятник: ось ближе и запас больше - в крайней точке не открывался угол. */
.ad-banim-pendulum .ad-banner-bg { transform-origin: 50% -15%; }
@keyframes ad-pendulum {
  from { transform: perspective(500px) scale(calc(var(--ad-bg-scale, 1) * 1.42)) rotateZ(-2.5deg) rotateY(-6deg); }
  to   { transform: perspective(500px) scale(calc(var(--ad-bg-scale, 1) * 1.42)) rotateZ(2.5deg) rotateY(6deg); }
}
/* Лупа: размер увеличенного круга ровно совпадает с оправой (от высоты баннера). */
.ad-banim-lens::before { width: auto; height: 60%; }
@keyframes ad-lens {
  0%, 100% { clip-path: circle(6.7% at 22% 48%); transform-origin: 22% 48%; }
  33%      { clip-path: circle(6.7% at 58% 36%); transform-origin: 58% 36%; }
  66%      { clip-path: circle(6.7% at 80% 62%); transform-origin: 80% 62%; }
}
/* Маятник: без поворота в глубину - он открывал верхний край. Только качание вокруг
   точки подвеса над кадром, с большим запасом. */
.ad-banim-pendulum .ad-banner-bg { transform-origin: 50% 0%; }
@keyframes ad-pendulum {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.4)) rotate(-3.2deg); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.4)) rotate(3.2deg); }
}


/* Удар: без вспышки (Кирилл 2026-09-17) - остаются сплющивание и толчок. */
.ad-tanim-zoomin::before { content: none !important; display: none !important; animation: none !important; }

/* ═══ Фон под портрет и товар (2026-09-17) ════════════════════════════════════
   На баннере чаще всего лицо или товар. Поэтому движения - только работа камеры вокруг
   цельного кадра: наезды, проезды, фокус, лёгкий поворот и покачивание. Точка интереса
   (центр кадра) всегда остаётся в кадре, лицо не искажается. */

/* НАЕЗД. Камера медленно подъезжает к лицу, задерживается, затем незаметно
   возвращается - и снова подъезжает. Как в интервью, когда оператор приближает героя. */
.ad-banim-pushin .ad-banner-bg {
  transform-origin: 50% 42%;
  animation: ad-pushin calc(var(--ad-bt, 1s) * 11) cubic-bezier(.4, 0, .2, 1) infinite;
}
@keyframes ad-pushin {
  0%        { transform: scale(calc(var(--ad-bg-scale, 1) * 1.04)); }
  55%, 75%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.3)); }
  100%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.04)); }
}

/* ОТЪЕЗД. Наоборот: кадр начинается крупным планом и камера плавно отъезжает,
   раскрывая всю картинку. */
.ad-banim-pullout .ad-banner-bg {
  transform-origin: 50% 42%;
  animation: ad-pullout calc(var(--ad-bt, 1s) * 11) cubic-bezier(.4, 0, .2, 1) infinite;
}
@keyframes ad-pullout {
  0%        { transform: scale(calc(var(--ad-bg-scale, 1) * 1.34)); }
  55%, 75%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.04)); }
  100%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.34)); }
}

/* ПРОЕЗД. Камера едет вбок вдоль кадра на слегка приближенном плане, как по рельсам,
   и так же плавно возвращается. */
.ad-banim-truck .ad-banner-bg {
  animation: ad-truck calc(var(--ad-bt, 1s) * 12) cubic-bezier(.45, 0, .55, 1) infinite alternate;
}
@keyframes ad-truck {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.2)) translateX(-6%); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.2)) translateX(6%); }
}

/* ФОКУС. Приём «перевод фокуса» из кино: кадр чуть расфокусирован и отодвинут, затем
   объектив наводит резкость и кадр мягко подаётся вперёд - и снова. Цвет не меняется. */
.ad-banim-focus .ad-banner-bg {
  animation: ad-focus calc(var(--ad-bt, 1s) * 6) ease-in-out infinite;
}
@keyframes ad-focus {
  0%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)); filter: blur(calc(var(--ad-blur, 0) * 1px + 3px)); }
  40%, 70% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.2)); filter: blur(calc(var(--ad-blur, 0) * 1px)); }
}

/* ПОВОРОТ. Кадр слегка разворачивается в объёме к зрителю и обратно, как портрет,
   который поворачивают к свету, - с лёгким наездом в конце поворота. */
.ad-banim-turn .ad-banner-bg {
  animation: ad-turn calc(var(--ad-bt, 1s) * 8) cubic-bezier(.45, 0, .55, 1) infinite alternate;
}
@keyframes ad-turn {
  from { transform: perspective(700px) scale(calc(var(--ad-bg-scale, 1) * 1.22)) rotateY(-7deg) translateX(-2%); }
  to   { transform: perspective(700px) scale(calc(var(--ad-bg-scale, 1) * 1.3)) rotateY(7deg) translateX(2%); }
}

/* ДЫХАНИЕ. Кадр едва заметно приближается и отдаляется в ритме спокойного дыхания -
   живое фото без явного движения. */
.ad-banim-breath .ad-banner-bg {
  transform-origin: 50% 45%;
  animation: ad-breath calc(var(--ad-bt, 1s) * 5) ease-in-out infinite;
}
@keyframes ad-breath {
  0%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.05)) translateY(0); }
  50%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.12)) translateY(-1%); }
}

/* КАЧКА и НЕВЕСОМОСТЬ - спокойнее: для лица сильный крен выглядит как ошибка. */
@keyframes ad-boat {
  0%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translateY(0) rotate(-1.4deg); }
  25%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translateY(-2%) rotate(0deg); }
  50%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translateY(0) rotate(1.4deg); }
  75%      { transform: scale(calc(var(--ad-bg-scale, 1) * 1.18)) translateY(2%) rotate(0deg); }
}
@keyframes ad-zerog {
  0%   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) translate(0, 0) rotate(0deg); }
  25%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.2)) translate(-2%, 1.5%) rotate(-1deg); }
  50%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.17)) translate(2%, 2%) rotate(.8deg); }
  75%  { transform: scale(calc(var(--ad-bg-scale, 1) * 1.21)) translate(1.5%, -2%) rotate(1.2deg); }
  100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.16)) translate(0, 0) rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .ad-banim-pushin .ad-banner-bg, .ad-banim-pullout .ad-banner-bg, .ad-banim-truck .ad-banner-bg,
  .ad-banim-focus .ad-banner-bg, .ad-banim-turn .ad-banner-bg, .ad-banim-breath .ad-banner-bg { animation: none; }
}
@keyframes ad-pendulum {
  from { transform: scale(calc(var(--ad-bg-scale, 1) * 1.55)) rotate(-2.6deg); }
  to   { transform: scale(calc(var(--ad-bg-scale, 1) * 1.55)) rotate(2.6deg); }
}
.ad-banim-pendulum .ad-banner-bg { transform-origin: 50% 20%; }


/* ═══ Эффекты кадра (2026-09-17) ══════════════════════════════════════════════
   Не движение камеры, а свет и стиль изображения: затухание, мигающая лампа,
   подсветка, неон, вспышки папарацци, проявление полароида, старый ТВ, плёнка, VHS.
   Все эффекты - поверх ЦЕЛОГО снимка, кадр не режется. Размытие из настроек
   сохраняется: оно входит в каждый фильтр. */

.ad-banim-oldtv .ad-banner-bg::before, .ad-banim-oldtv .ad-banner-bg::after,
.ad-banim-film .ad-banner-bg::before, .ad-banim-film .ad-banner-bg::after,
.ad-banim-vhs .ad-banner-bg::before, .ad-banim-vhs .ad-banner-bg::after,
.ad-banim-paparazzi .ad-banner-bg::after, .ad-banim-backlight .ad-banner-bg::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
}

/* ЗАТУХАНИЕ. Свет в кадре медленно гаснет почти до полутьмы и так же плавно
   возвращается - как будто за окном проходит облако. */
.ad-banim-fade .ad-banner-bg { animation: fx-fade calc(var(--ad-bt, 1s) * 6) ease-in-out infinite; }
@keyframes fx-fade {
  0%, 20%, 100% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1); }
  55%, 65%      { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(.42); }
}

/* МИГАЮЩАЯ ЛАМПА. Кадр освещён лампой, которая вот-вот перегорит: горит ровно, потом
   неровно подмигивает, проседает, вспыхивает - и снова ровно. */
.ad-banim-flicker .ad-banner-bg { animation: fx-flicker calc(var(--ad-bt, 1s) * 4) steps(1, end) infinite; }
@keyframes fx-flicker {
  0%, 56%, 100% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1); }
  58% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(.45); }
  60% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1.08); }
  62% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(.3); }
  66% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(.95); }
  70% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(.55); }
  72% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1.12); }
  76% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1); }
  88% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(.7); }
  90% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1); }
}

/* ПОДСВЕТКА. Мягкий тёплый свет медленно «ходит» по кадру, как софтбокс в студии:
   освещает то одну сторону лица, то другую. */
.ad-banim-backlight .ad-banner-bg::after {
  background: radial-gradient(ellipse 55% 110% at 20% 40%, rgba(255, 230, 190, .5), rgba(255, 230, 190, 0) 70%);
  mix-blend-mode: soft-light;
  animation: fx-backlight calc(var(--ad-bt, 1s) * 7) ease-in-out infinite alternate;
}
.ad-banim-backlight .ad-banner-bg { animation: fx-backlight-base calc(var(--ad-bt, 1s) * 7) ease-in-out infinite alternate; }
@keyframes fx-backlight { from { transform: translateX(-10%); opacity: .6; } to { transform: translateX(70%); opacity: 1; } }
@keyframes fx-backlight-base {
  from { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(.9); }
  to   { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1.05); }
}

/* НЕОНОВАЯ РАМКА. По краю баннера светится неоновая трубка: разгорается, подрагивает
   и мягко пульсирует - как вывеска ночного магазина. */
.ad-banim-neonedge::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 0 0 2px rgba(120, 255, 214, .95), inset 0 0 14px rgba(60, 240, 190, .75), inset 0 0 34px rgba(40, 220, 170, .35);
  animation: fx-neon calc(var(--ad-bt, 1s) * 4) steps(1, end) infinite;
}
@keyframes fx-neon {
  0%, 100% { opacity: 1; }
  8%  { opacity: .35; }
  10% { opacity: 1; }
  12% { opacity: .5; }
  14%, 60% { opacity: 1; }
  62% { opacity: .75; }
  64%, 99% { opacity: 1; }
}

/* ПАПАРАЦЦИ. Время от времени вспыхивают фотовспышки - одна, серия из двух - и кадр
   чуть вздрагивает от каждой, как на красной дорожке. */
.ad-banim-paparazzi .ad-banner-bg { animation: fx-pap-zoom calc(var(--ad-bt, 1s) * 5) ease-out infinite; }
.ad-banim-paparazzi .ad-banner-bg::after {
  background: rgba(255, 255, 255, .95);
  opacity: 0;
  animation: fx-pap calc(var(--ad-bt, 1s) * 5) linear infinite;
}
@keyframes fx-pap {
  0%, 18%, 23%, 52%, 55.5%, 60%, 63.5%, 100% { opacity: 0; }
  19%   { opacity: .85; }
  53%   { opacity: .7; }
  61%   { opacity: .9; }
}
@keyframes fx-pap-zoom {
  0%, 18%, 30%, 52%, 70%, 100% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)); }
  19.5% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.1)); }
  61.5% { transform: scale(calc(var(--ad-bg-scale, 1) * 1.1)); }
}

/* ПОЛАРОИД. Снимок проявляется, как моментальное фото: из белёсого и бесцветного
   медленно набирает контраст и цвет, держится - и проявляется заново. */
.ad-banim-polaroid .ad-banner-bg { animation: fx-polaroid calc(var(--ad-bt, 1s) * 9) ease-in-out infinite; }
@keyframes fx-polaroid {
  0%      { filter: blur(calc(var(--ad-blur, 0) * 1px + 1.5px)) brightness(1.9) saturate(0) contrast(.6); }
  45%, 85% { filter: blur(calc(var(--ad-blur, 0) * 1px)) brightness(1) saturate(1) contrast(1); }
  100%    { filter: blur(calc(var(--ad-blur, 0) * 1px + 1.5px)) brightness(1.9) saturate(0) contrast(.6); }
}

/* СТАРЫЙ ТЕЛЕВИЗОР. Чёрно-белый кинескоп: строки развёртки, шумная «снежинка»,
   по экрану ползёт светлая полоса, картинка слегка дрожит и мерцает. */
.ad-banim-oldtv .ad-banner-bg { animation: fx-tv calc(var(--ad-bt, 1s) * .18) steps(2, end) infinite; }
@keyframes fx-tv {
  0%   { filter: blur(calc(var(--ad-blur, 0) * 1px + .3px)) grayscale(1) contrast(1.35) brightness(.95); transform: scale(calc(var(--ad-bg-scale, 1) * 1.04)) translate(0, 0); }
  50%  { filter: blur(calc(var(--ad-blur, 0) * 1px + .3px)) grayscale(1) contrast(1.3) brightness(1.02); transform: scale(calc(var(--ad-bg-scale, 1) * 1.04)) translate(.3%, -.2%); }
  100% { filter: blur(calc(var(--ad-blur, 0) * 1px + .3px)) grayscale(1) contrast(1.35) brightness(.95); transform: scale(calc(var(--ad-bg-scale, 1) * 1.04)) translate(0, 0); }
}
.ad-banim-oldtv .ad-banner-bg::before {
  background-image:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, .28) 0 1px, rgba(0, 0, 0, 0) 1px 3px),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: auto, 160px 160px;
  opacity: .55;
  animation: fx-noise .35s steps(4, end) infinite;
}
.ad-banim-oldtv .ad-banner-bg::after {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .22) 50%, rgba(255, 255, 255, 0));
  height: 30%; inset: auto 0 auto 0; top: -30%;
  animation: fx-tv-bar calc(var(--ad-bt, 1s) * 5) linear infinite;
}
@keyframes fx-tv-bar { from { top: -30%; } to { top: 110%; } }
@keyframes fx-noise {
  0%   { background-position: 0 0, 0 0; }
  25%  { background-position: 0 0, -40px 30px; }
  50%  { background-position: 0 0, 50px -20px; }
  75%  { background-position: 0 0, -30px -50px; }
  100% { background-position: 0 0, 20px 40px; }
}

/* КИНОПЛЁНКА. Старое кино: тёплая выцветшая плёнка, зерно, по кадру пробегают
   вертикальные царапины, изображение чуть «гуляет» в проекторе, края темнеют. */
.ad-banim-film .ad-banner-bg { animation: fx-film calc(var(--ad-bt, 1s) * .5) steps(3, end) infinite; }
@keyframes fx-film {
  0%   { filter: blur(calc(var(--ad-blur, 0) * 1px)) sepia(.55) contrast(1.1) brightness(.97); transform: scale(calc(var(--ad-bg-scale, 1) * 1.05)) translate(0, 0); }
  33%  { filter: blur(calc(var(--ad-blur, 0) * 1px)) sepia(.55) contrast(1.12) brightness(1.02); transform: scale(calc(var(--ad-bg-scale, 1) * 1.05)) translate(-.2%, .3%); }
  66%  { filter: blur(calc(var(--ad-blur, 0) * 1px)) sepia(.55) contrast(1.08) brightness(.94); transform: scale(calc(var(--ad-bg-scale, 1) * 1.05)) translate(.2%, -.1%); }
  100% { filter: blur(calc(var(--ad-blur, 0) * 1px)) sepia(.55) contrast(1.1) brightness(.97); transform: scale(calc(var(--ad-bg-scale, 1) * 1.05)) translate(0, 0); }
}
.ad-banim-film .ad-banner-bg::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>"), radial-gradient(ellipse at center, rgba(0, 0, 0, 0) 55%, rgba(20, 10, 0, .55) 100%);
  background-size: 160px 160px, 100% 100%;
  mix-blend-mode: multiply; opacity: .7;
  animation: fx-noise .3s steps(3, end) infinite;
}
.ad-banim-film .ad-banner-bg::after {
  background:
    linear-gradient(90deg, transparent 0 calc(var(--sx, 30%) - .5px), rgba(255, 250, 235, .55) calc(var(--sx, 30%) - .5px) calc(var(--sx, 30%) + .5px), transparent calc(var(--sx, 30%) + .5px)),
    linear-gradient(90deg, transparent 0 71.6%, rgba(20, 10, 0, .5) 71.6% 71.9%, transparent 71.9%);
  animation: fx-scratch calc(var(--ad-bt, 1s) * 1.4) steps(1, end) infinite;
}
@keyframes fx-scratch {
  0%   { opacity: .9; transform: translateX(0); }
  20%  { opacity: 0; }
  35%  { opacity: .8; transform: translateX(-38%); }
  55%  { opacity: 0; }
  70%  { opacity: .7; transform: translateX(22%); }
  85%  { opacity: 0; }
}

/* VHS-КАССЕТА. Кадр как на затёртой видеокассете: цветные каналы расползаются в
   стороны, по экрану ползёт полоса «трекинга», картинка иногда дёргается. */
.ad-banim-vhs .ad-banner-bg { animation: fx-vhs calc(var(--ad-bt, 1s) * 3) steps(1, end) infinite; }
@keyframes fx-vhs {
  0%, 40%, 46%, 100% { filter: blur(calc(var(--ad-blur, 0) * 1px + .4px)) saturate(1.3) contrast(1.05); transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)) translateX(0); }
  42% { filter: blur(calc(var(--ad-blur, 0) * 1px + .4px)) saturate(1.3) contrast(1.05); transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)) translateX(-1.2%); }
  44% { filter: blur(calc(var(--ad-blur, 0) * 1px + .4px)) saturate(1.3) contrast(1.05); transform: scale(calc(var(--ad-bg-scale, 1) * 1.06)) translateX(.8%); }
}
.ad-banim-vhs .ad-banner-bg::before {
  background: inherit; background-size: cover; background-position: center;
  mix-blend-mode: screen; opacity: .45;
  filter: sepia(1) saturate(6) hue-rotate(-45deg);
  animation: fx-vhs-r calc(var(--ad-bt, 1s) * 2) ease-in-out infinite alternate;
}
.ad-banim-vhs .ad-banner-bg::after {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, .16) 45%, rgba(0, 0, 0, .18) 55%, rgba(255, 255, 255, 0) 100%),
    repeating-linear-gradient(0deg, rgba(0, 0, 0, .12) 0 1px, rgba(0, 0, 0, 0) 1px 4px);
  background-size: 100% 22%, auto; background-repeat: no-repeat, repeat;
  animation: fx-vhs-track calc(var(--ad-bt, 1s) * 4) linear infinite;
}
@keyframes fx-vhs-r { from { transform: translate(-3px, 0); } to { transform: translate(3px, 1px); } }
@keyframes fx-vhs-track { from { background-position: 0 -25%, 0 0; } to { background-position: 0 125%, 0 0; } }

@media (prefers-reduced-motion: reduce) {
  .ad-banim-fade .ad-banner-bg, .ad-banim-flicker .ad-banner-bg, .ad-banim-backlight .ad-banner-bg,
  .ad-banim-backlight .ad-banner-bg::after, .ad-banim-neonedge::before, .ad-banim-paparazzi .ad-banner-bg,
  .ad-banim-paparazzi .ad-banner-bg::after, .ad-banim-polaroid .ad-banner-bg, .ad-banim-oldtv .ad-banner-bg,
  .ad-banim-oldtv .ad-banner-bg::before, .ad-banim-oldtv .ad-banner-bg::after, .ad-banim-film .ad-banner-bg,
  .ad-banim-film .ad-banner-bg::before, .ad-banim-film .ad-banner-bg::after, .ad-banim-vhs .ad-banner-bg,
  .ad-banim-vhs .ad-banner-bg::before, .ad-banim-vhs .ad-banner-bg::after { animation: none; }
}

/* ── Карточка поставщика: ссылки и правка контактов ──────────────────────── */
.sup-open-btn {
  flex: 0 0 auto; border: 1px solid rgba(15, 107, 91, .35); background: #fff; color: var(--pine, #0E6B5B);
  border-radius: 999px; padding: 5px 12px; font: 600 12.5px/1 inherit; font-family: inherit; cursor: pointer;
}
.sup-open-btn:active { transform: scale(.95); }
.sup-row-actions { justify-content: flex-start; border-top: 1px solid rgba(0, 0, 0, .06); }
.sup-row-actions .sup-edit-btn { padding: 8px 14px; font-size: 13.5px; }
.sup-field-cap { font-size: 12.5px; color: var(--fg-dim); margin: 2px 2px 4px; }
#supContCats { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
#supContHint { color: #dc2626; }
.sp-wa .sp-contact-ico { background: rgba(37, 211, 102, .16); }
.sp-vk .sp-contact-ico { background: rgba(0, 119, 255, .14); }
.sp-wa .sp-contact-ico::before { content: "🟢"; }
.sp-vk .sp-contact-ico::before { content: "🌐"; }

/* Единая форма профиля поставщика. */
.sup-edit-all { margin-top: 12px; }
.sup-sec { margin-top: 18px; padding-top: 14px; border-top: 1px solid rgba(0, 0, 0, .07); }
#supplierBioInput { resize: vertical; min-height: 92px; }
#supEditHint { color: #dc2626; margin-top: 6px; }
.sup-edit-actions { position: sticky; bottom: 0; padding: 10px 0 2px; background: var(--bg); }
.sup-bio { margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0, 0, 0, .06);
  font-size: 14.5px; line-height: 1.45; color: var(--fg); white-space: pre-wrap; }
.sup-bio-cap { font-size: 12.5px; color: var(--fg-dim); margin-bottom: 3px; }
.sup-bio-empty { color: var(--fg-dim); font-size: 13.5px; }

/* Страница товара плотнее (Кирилл 2026-09-17): категория слева и рынок справа одной
   строкой, сразу под ней описание, под описанием почти вплотную - кнопка контакта. */
#productBody .product-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: 10px 0 4px; line-height: 1.35;
}
#productBody .product-meta-line { margin: 0 !important; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#productBody .product-meta-line + .product-meta-line { text-align: right; flex: 0 1 auto; }
#productBody .product-desc { margin-top: 4px; margin-bottom: 8px; }
#productBody .product-meta:last-child { margin-bottom: 8px; }
#productBody .product-desc + * { margin-top: 0; }
#productContactBox { margin-top: 0 !important; padding-top: 0 !important; }
#productContactBox .product-contact-btn { margin-top: 4px; }
#productBody { padding-bottom: 0; margin-bottom: 0; }

/* Название ближе к фото; строка «категория · рынок» с воздухом сверху и снизу. */
#productBody .product-title-row { margin-bottom: 4px; }
#productBody .product-meta { margin: 12px 0 8px; }
#productBody .respond-video { margin-top: 6px !important; }
#productBody .product-title-row { margin-bottom: 0 !important; }
/* Строка названия росла из-за выравнивания по базовой линии с крупной ценой - лишние
   14px пустоты над фото. Выравниваем по верху. */
#productBody .product-title-row { align-items: flex-start; }
#productBody .product-title-price { line-height: 1.25; }
#productBody .product-title-row .product-title-top { margin: 0 !important; }
/* Название чуть ближе к строке «Назад». */
#productView .product-topbar { margin-bottom: 5px; }

/* Обсуждение под товаром: плотнее к разделителю, инструменты правки в шапке сообщения. */
#productView .projects-divider { margin: 14px 0 10px; }
#productCommentsTitle { margin: 0 4px 6px; }
.comment-head { position: relative; }
.comment-tools { margin-left: auto; display: flex; gap: 4px; }
.comment-tool {
  display: inline-flex; align-items: center; gap: 3px; border: none; background: none;
  color: var(--fg-dim); font: 600 12px/1 inherit; font-family: inherit; padding: 5px 6px; border-radius: 8px; cursor: pointer;
}
.comment-tool .line-ico { width: 13px; height: 13px; }
.comment-tool:active { background: rgba(0, 0, 0, .06); }
.comment-tool.is-danger { color: #dc2626; }
.comment-edited { font-size: 12px; color: var(--fg-dim); }
.comment-edit textarea {
  width: 100%; box-sizing: border-box; margin-top: 6px; padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--card); font: 15px/1.4 inherit; font-family: inherit; color: var(--fg); resize: vertical;
}
.comment-edit .hint { color: #dc2626; }
.comment-edit-row { display: flex; gap: 8px; margin-top: 6px; }
.comment-edit-row .idea-btn { flex: 0 0 auto; width: auto; padding: 0 16px; }
.comment-edit-row .submit-btn { flex: 1; margin: 0; }


/* ═══ Заявка «Сейчас ищут» - страницей, как товар витрины (2026-09-17) ═══════════ */
.detail-overlay.detail-page {
  inset: auto 0 0 0; top: 0;
  background: var(--bg-page, #f3f4f6);
  background-image: inherit;
  display: block; overflow-y: auto; -webkit-overflow-scrolling: touch;
  align-items: stretch;
}
body .detail-overlay.detail-page { background: linear-gradient(180deg, #eef0f2, #e6e8eb); }
.detail-page .detail-card {
  max-width: 560px; margin: 0 auto; max-height: none; overflow: visible;
  border-radius: 0; background: transparent; box-shadow: none;
  padding: 10px 16px 28px; box-sizing: border-box;
  animation: none;
}
.detail-page .product-topbar { margin-bottom: 5px; }
.detail-page .product-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin: 0; }
.detail-page .product-title-row .product-title-top { margin: 0 !important; font-size: 20px; }
.detail-page .detail-photo-wrap { position: relative; margin-top: 6px; }
.detail-page .detail-photo {
  display: block; width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  border-radius: 14px; background: #000; max-height: none;
}
.detail-page .detail-nav { top: 50%; }
.detail-page .detail-meta-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: 12px 0 8px; color: var(--fg-dim); font-size: 14.5px;
}
.detail-page .detail-meta-row .product-meta-line { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.detail-page .detail-body { padding: 0; }
.detail-page .product-desc { margin: 0 0 4px; font-size: 14.5px; color: var(--fg-dim); line-height: 1.5; }
.detail-page .detail-status { font-size: 13px; margin: 0 0 8px; }
.detail-page .detail-action { margin-top: 4px; display: flex; align-items: center; justify-content: center; gap: 6px; }
.detail-page .detail-action .line-ico { width: 20px; height: 20px; }
.detail-page .projects-divider { margin: 14px 0 10px; }
.detail-page .detail-photo { height: auto !important; aspect-ratio: 3 / 4 !important; max-height: none !important; }
.detail-page .detail-photo-wrap { aspect-ratio: 3 / 4; border-radius: 14px; overflow: hidden; background: #000; }
.detail-page .detail-photo { height: 100% !important; aspect-ratio: auto !important; border-radius: 0; }
/* «Назад» на странице заявки - обычная кнопка в строке, а не круглый крестик на фото. */
.detail-page .detail-close { position: static; width: auto; height: auto; border-radius: 999px; }

/* Метки на большом фото заявки - те же, что на плитке, только на своих местах. */
.detail-page .detail-photo-wrap .tile-badge { position: absolute; z-index: 2; }
.detail-page .detail-badge-left   { top: 12px; left: 12px; right: auto; bottom: auto; }
.detail-page .detail-badge-views  { bottom: 12px; left: 12px; top: auto; right: auto; }
.detail-page .detail-badge-offers { bottom: 12px; right: 12px; top: auto; left: auto; }
.detail-page .detail-nav { z-index: 3; }
.detail-page .product-desc { color: var(--fg-dim); }
/* На большом фото метки крупнее, чтобы читались как на плитке по пропорции. */
.detail-page .detail-photo-wrap .tile-badge { font-size: 13.5px; padding: 5px 11px; gap: 5px; }
.detail-page .detail-photo-wrap .tile-badge .line-ico { width: 15px; height: 15px; }


/* Предложения поставщиков под заявкой - карточки в стиле обсуждения витрины. */
.offer-card2 { display: block; margin-bottom: 10px; }
.offer-card2 .offer-avatar { flex: 0 0 auto; width: 40px; height: 40px; font-size: 16px; }
.offer-card2 .comment-who .verif-tag { margin-left: 6px; vertical-align: middle; }
.offer-card2 .offer-price-qty { font-size: 16px; font-weight: 700; margin: 2px 0 8px; }
.offer-card2 .offer-photo2 { margin: 0 0 8px; }
.offer-card2 .offer-comment2 { color: var(--fg-dim); margin-bottom: 8px; }
.offer-card2 .offer-write-btn { margin: 2px 0 0; display: flex; align-items: center; justify-content: center; gap: 6px; }
.offer-card2 .offer-write-btn .line-ico { width: 18px; height: 18px; }
.offer-card2 .offer-hint2 { margin-top: 6px; }

/* Витрина: метки на фото товара. */
.product-photo-wrap { position: relative; }
.product-photo-wrap .tile-badge { position: absolute; z-index: 2; font-size: 13.5px; padding: 5px 11px; gap: 5px; }
.product-photo-wrap .tile-badge .line-ico { width: 15px; height: 15px; }
.product-photo-wrap .pp-badge-views { left: 12px; bottom: 12px; top: auto; }
.product-photo-wrap .pp-badge-comments { right: 12px; bottom: 12px; top: auto; left: auto; }
#productBody .product-photo-wrap { margin-top: 6px; }
#productBody .product-photo-wrap .respond-video { margin-top: 0 !important; }


/* ── Мои сообщения: архив и смахивание ─────────────────────────────────────── */
.dlg-swipe {
  position: relative; margin-bottom: 8px; border-radius: 14px; overflow: hidden;
  max-height: 200px; transition: max-height .3s ease, opacity .25s ease, margin .3s ease;
}
.dlg-swipe .dlg-row { position: relative; z-index: 1; margin: 0; will-change: transform; }
.dlg-swipe.is-gone { max-height: 0 !important; opacity: 0; margin-bottom: 0; }
.dlg-swipe-actions {
  position: absolute; inset: 0; display: flex; justify-content: flex-end; align-items: stretch;
  background: var(--card);
}
.dlg-act {
  width: 88px; border: none; color: #fff; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px; font: 600 12px/1 inherit; font-family: inherit; cursor: pointer;
}
.dlg-act .line-ico { width: 20px; height: 20px; }
.dlg-act.is-archive { background: linear-gradient(135deg, #64748b, #475569); width: 96px; }
.dlg-act.is-restore { background: linear-gradient(135deg, #16a34a, #0E6B5B); }
.dlg-act.is-delete  { background: linear-gradient(135deg, #ef4444, #dc2626); }
.dlg-act:active { filter: brightness(.92); }

.dlg-archive-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px; margin-bottom: 10px; border: none; border-radius: 14px;
  background: linear-gradient(135deg, rgba(100, 116, 139, .12), rgba(100, 116, 139, .05));
  font-family: inherit; cursor: pointer; text-align: left;
  animation: earnSubsIn .3s cubic-bezier(.16, 1, .3, 1) both;
}
.dlg-archive-ico {
  width: 46px; height: 46px; border-radius: 50%; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  background: #64748b; color: #fff;
}
.dlg-archive-ico .line-ico { width: 22px; height: 22px; }
.dlg-archive-title { flex: 1 1 auto; font-size: 15px; font-weight: 800; color: var(--fg); }
.dlg-archive-count {
  min-width: 22px; height: 22px; padding: 0 7px; border-radius: 11px; box-sizing: border-box;
  background: #94a3b8; color: #fff; font-size: 12px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
.dlg-archive-go { color: var(--fg-dim); display: flex; }
.dlg-archive-go .line-ico { width: 18px; height: 18px; }
.dlg-archive-row:active { transform: scale(.98); }

.dlg-folder-back {
  display: inline-flex; align-items: center; gap: 4px; margin: 0 0 10px; padding: 8px 12px 8px 8px;
  border: none; border-radius: 999px; background: rgba(100, 116, 139, .12);
  color: var(--fg); font: 600 14px/1 inherit; font-family: inherit; cursor: pointer;
}
.dlg-folder-back .line-ico { width: 18px; height: 18px; }
.dlg-swipe-tip { text-align: center; font-size: 12.5px; color: var(--fg-dim); margin: 6px 0 2px; opacity: .8; }
.dlg-folder-back { width: auto !important; display: inline-flex !important; }


/* ═══ Чат: визуальный отклик на события (2026-09-17) ═══════════════════════════ */

/* Отправка: от кнопки расходится мягкая волна, стрелка «выстреливает» вверх. */
#chatSendBtn { position: relative; overflow: visible; }
#chatSendBtn.is-fired::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  box-shadow: 0 0 0 0 rgba(22, 163, 74, .45);
  animation: fxSendWave .55s ease-out;
}
@keyframes fxSendWave { to { box-shadow: 0 0 0 16px rgba(22, 163, 74, 0); } }
#chatSendBtn.is-fired svg, #chatSendBtn.is-fired .line-ico { animation: fxSendArrow .45s cubic-bezier(.3, 1.4, .5, 1); }
@keyframes fxSendArrow { 0% { transform: none; } 35% { transform: translateY(-5px) scale(.9); } 100% { transform: none; } }

/* Своё только что отправленное сообщение - «приземляется» с лёгким пружинным отскоком
   и коротким свечением края. */
.chat-msg.is-mine.is-sent .chat-bubble { animation: fxSentPop .5s cubic-bezier(.2, 1.5, .4, 1) both; }
@keyframes fxSentPop {
  0%   { transform: translateY(10px) scale(.92); box-shadow: 0 0 0 0 rgba(22, 163, 74, .35); }
  60%  { transform: translateY(-2px) scale(1.02); box-shadow: 0 0 0 6px rgba(22, 163, 74, 0); }
  100% { transform: none; }
}

/* Ответили мне: пузырь дважды мягко подсвечивается зелёным кольцом и остаётся с тонкой
   зелёной меткой слева - видно, где обратились лично. */
.chat-msg.is-to-me .chat-bubble {
  box-shadow: inset 3px 0 0 #16a34a;
  animation: fxToMe 1.6s ease-out 2;
}
@keyframes fxToMe {
  0%   { box-shadow: inset 3px 0 0 #16a34a, 0 0 0 0 rgba(22, 163, 74, .5); }
  60%  { box-shadow: inset 3px 0 0 #16a34a, 0 0 0 9px rgba(22, 163, 74, 0); }
  100% { box-shadow: inset 3px 0 0 #16a34a, 0 0 0 0 rgba(22, 163, 74, 0); }
}

/* Колокольчик: при нажатии качается, как настоящий. */
#chatSoundBtn.is-rung .line-ico { transform-origin: 50% 10%; animation: fxBell .6s ease-in-out; }
@keyframes fxBell {
  0%, 100% { transform: rotate(0); }
  20% { transform: rotate(-18deg); } 40% { transform: rotate(14deg); }
  60% { transform: rotate(-8deg); } 80% { transform: rotate(4deg); }
}

@media (prefers-reduced-motion: reduce) {
  #chatSendBtn.is-fired::after, #chatSendBtn.is-fired svg, .chat-msg.is-mine.is-sent .chat-bubble,
  .chat-msg.is-to-me .chat-bubble, #chatSoundBtn.is-rung .line-ico { animation: none; }
}


/* ── Выбор набора звуков чата ─────────────────────────────────────────────── */
.sndpk-overlay {
  position: fixed; inset: 0; z-index: 900; background: rgba(15, 23, 42, .38);
  display: flex; align-items: flex-end; justify-content: center;
  animation: sndpkFade .2s ease both;
}
.sndpk-overlay.is-closing { animation: sndpkFade .2s ease reverse both; }
@keyframes sndpkFade { from { opacity: 0; } to { opacity: 1; } }
.sndpk-sheet {
  width: 100%; max-width: 520px; box-sizing: border-box;
  background: var(--bg); border-radius: 22px 22px 0 0;
  padding: 18px 16px calc(16px + env(safe-area-inset-bottom));
  box-shadow: 0 -10px 40px rgba(0, 0, 0, .18);
  animation: sndpkUp .32s cubic-bezier(.16, 1, .3, 1) both;
}
@keyframes sndpkUp { from { transform: translateY(40px); } to { transform: none; } }
.sndpk-title { font-size: 18px; font-weight: 800; text-align: center; }
.sndpk-sub { font-size: 13px; color: var(--fg-dim); text-align: center; margin: 4px 0 14px; }
.sndpk-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.sndpk-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 13px 14px; border-radius: 14px; border: 1px solid var(--border);
  background: var(--card); font: 600 15px/1 inherit; font-family: inherit; color: var(--fg); cursor: pointer;
  transition: border-color .2s ease, background-color .2s ease, transform .15s ease;
}
.sndpk-item:active { transform: scale(.97); }
.sndpk-check { opacity: 0; color: var(--pine, #0E6B5B); display: flex; transition: opacity .2s ease; }
.sndpk-check .line-ico { width: 18px; height: 18px; stroke-width: 2.6; }
.sndpk-item.is-on { border-color: var(--pine, #0E6B5B); background: rgba(14, 107, 91, .08); }
.sndpk-item.is-on .sndpk-check { opacity: 1; }
.sndpk-done { margin-top: 14px; }

/* Перечёркнутый колокольчик ЗЕРКАЛЬНЫЙ (черта в правую сторону) - и во время качания
   тоже. Прежняя анимация писала поворот без отражения, и черта на миг/насовсем
   разворачивалась в другую сторону. */
#chatSoundBtn.is-rung .line-ico.is-mirror { animation-name: fxBellM; }
@keyframes fxBellM {
  0%, 100% { transform: scaleX(-1) rotate(0); }
  20% { transform: scaleX(-1) rotate(-18deg); } 40% { transform: scaleX(-1) rotate(14deg); }
  60% { transform: scaleX(-1) rotate(-8deg); } 80% { transform: scaleX(-1) rotate(4deg); }
}
.chat-sound .line-ico.is-mirror { transform: scaleX(-1); }

/* Стрелки ‹ › на фото товара витрины - как на заявках. */
.product-photo-wrap .product-nav { position: absolute; top: 50%; z-index: 3; }
.product-photo-wrap .detail-nav-prev { left: 10px; }
.product-photo-wrap .detail-nav-next { right: 10px; }
/* Кнопка «Загрузить на поиск» переехала наверх - старый верхний отступ от нижней кнопки
   опускал её ниже «Назад». */
.screen-topbar #feedToFormBtn { margin: 0 0 0 auto; width: auto; }


/* ═══ Обсуждение товара: вопрос и ответы одним облачком (2026-09-17) ═══════════ */
.comment-card { padding: 14px 14px 10px; }
.comment-card .comment-head { margin-bottom: 4px; }
.comment-tools { margin-left: auto; gap: 2px; }
.comment-tool { width: 32px; height: 32px; padding: 0; justify-content: center; border-radius: 50%; }
.comment-tool .line-ico { width: 17px; height: 17px; }

/* Ответы - строки внутри карточки вопроса, отделены тонкой линией и лёгким сдвигом. */
.comment-thread:empty { display: none; }
.comment-thread { margin-top: 10px; padding-top: 2px; border-top: 1px solid rgba(0, 0, 0, .06); }
.comment-thread-reply {
  position: relative; padding: 10px 0 2px 14px; margin-left: 6px;
  border-left: 2px solid rgba(46, 124, 246, .35);
}
.comment-thread-reply + .comment-thread-reply { margin-top: 6px; }
.comment-thread-reply .comment-head { gap: 8px; margin-bottom: 2px; }
.comment-thread-reply .comment-head .ava-wrap,
.comment-thread-reply .comment-head .avatar-mark { transform: scale(.78); transform-origin: left center; margin-right: -8px; }
.comment-thread-reply .comment-who { font-size: 14px; }
.comment-thread-reply .comment-text { font-size: 14.5px; }

/* «Ответить» - тихая ссылка справа внизу вместо широкой кнопки на всю карточку. */
.comment-reply-link {
  display: flex; align-items: center; gap: 5px; margin: 8px 0 0 auto;
  border: none; background: none; padding: 6px 4px; cursor: pointer;
  color: var(--pine, #0E6B5B); font: 600 14px/1 inherit; font-family: inherit;
}
.comment-reply-link .line-ico { width: 16px; height: 16px; }
.comment-reply-link:active { opacity: .7; }
.comment-thread-reply .comment-head .ava-box { transform: scale(.75); transform-origin: left center; margin-right: -10px; }


/* ── Уведомление по центру экрана (заявка размещена) ──────────────────────── */
.center-notice {
  position: fixed; inset: 0; z-index: 950; display: flex; align-items: center; justify-content: center;
  padding: 24px; background: rgba(15, 23, 42, .18); animation: cnFade .22s ease both;
}
.center-notice.is-out { animation: cnFade .25s ease reverse both; }
@keyframes cnFade { from { opacity: 0; } to { opacity: 1; } }
.cn-card {
  width: 100%; max-width: 320px; box-sizing: border-box; text-align: center;
  padding: 24px 22px 22px; border-radius: 22px; background: var(--bg);
  box-shadow: 0 20px 50px rgba(15, 23, 42, .25);
  animation: cnPop .42s cubic-bezier(.2, 1.4, .4, 1) both;
}
.center-notice.is-out .cn-card { animation: cnPop .25s ease reverse both; }
@keyframes cnPop { from { transform: scale(.85); opacity: 0; } to { transform: none; opacity: 1; } }
.cn-ico {
  width: 58px; height: 58px; margin: 0 auto 12px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, #16a34a, #0E6B5B); color: #fff;
  box-shadow: 0 8px 20px rgba(14, 107, 91, .35);
}
.center-notice.is-wait .cn-ico { background: linear-gradient(135deg, #f59e0b, #d97706); box-shadow: 0 8px 20px rgba(217, 119, 6, .3); }
.cn-ico .line-ico { width: 30px; height: 30px; stroke-width: 2.4; }
.cn-title { font-size: 19px; font-weight: 800; color: var(--fg); }
.cn-text { margin-top: 6px; font-size: 14.5px; line-height: 1.4; color: var(--fg-dim); }

/* «Мои заявки»: две кнопки под плиткой - изменить и удалить. */
.my-request-acts { display: flex; gap: 6px; margin-top: 6px; }
.my-request-acts .my-request-del { flex: 1; margin: 0; }
.my-request-edit {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  border: 1px solid var(--border); background: var(--card); color: var(--fg);
  border-radius: 10px; padding: 7px 8px; font: 600 13px/1 inherit; font-family: inherit; cursor: pointer;
}
.my-request-edit .line-ico { width: 14px; height: 14px; }
.rfq-edit-sheet .sup-loc-row { gap: 8px; }
.screen-topbar #formMyRequestsBtn { margin: 0 0 0 auto; }


/* ── Лента и витрина: счётчик вместо заголовка, кнопка подачи под фильтрами ──── */
.feed-total { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 700; color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.screen-topbar .feed-total { margin-left: 10px; }
/* Блик с кнопки «Загрузить» снят: над ней на баннере уже идёт своё движение,
   и два бегающих отсвета на одном экране спорили друг с другом (Кирилл 2026-09-18). */
#catalogMarketFilter { min-width: 0; }

/* Галочка в уведомлении о заявке рисуется штрихом, кружок пружинит. */
.cn-ico { animation: cnIcoPop .5s cubic-bezier(.2, 1.5, .4, 1) both; }
@keyframes cnIcoPop { 0% { transform: scale(.3); opacity: 0; } 60% { transform: scale(1.12); opacity: 1; } 100% { transform: none; } }
.cn-check { width: 30px; height: 30px; }
.cn-check path { stroke-dasharray: 26; stroke-dashoffset: 26; animation: cnDraw .45s .22s ease-out forwards; }
@keyframes cnDraw { to { stroke-dashoffset: 0; } }
.center-notice.is-wait .cn-ico .line-ico { animation: cnHourglass 1.4s ease-in-out infinite; }
@keyframes cnHourglass { 0%, 60%, 100% { transform: rotate(0); } 75% { transform: rotate(180deg); } }
@media (prefers-reduced-motion: reduce) {
  .cn-ico, .cn-check path, .center-notice.is-wait .cn-ico .line-ico { animation: none; stroke-dashoffset: 0; }
}

/* Главная с настоящими объявлениями в исходных фирменных цветах.
   Вторичные разделы и оплаченные рекламные места сохраняются. */
body {
  background: var(--bg);
  background-image: none;
  animation: none;
}
.tile, .earn-card, .project-link, .balance-card {
  border: 1px solid var(--border);
  box-shadow: 0 3px 14px rgba(16, 24, 40, .04);
}
.home-view { max-width: 1180px; padding: 18px 16px calc(30px + var(--safe-b)); }
.market-home-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 18px; }
.market-home-brand { font-size: 25px; line-height: 1; font-weight: 900; letter-spacing: -.045em; color: var(--fg); }
.market-home-brand-in { color: var(--accent); }
.market-home-subtitle { margin-top: 5px; font-size: 12px; font-weight: 600; color: var(--fg-dim); }
.market-home-search { display: grid; grid-template-columns: minmax(0, 1fr) minmax(118px, 180px); gap: 8px; margin-bottom: 10px; }
.market-home-search-field { display: flex; align-items: center; gap: 8px; min-width: 0; border: 1px solid var(--border); border-radius: 13px; background: var(--card); padding: 0 12px; color: var(--fg-dim); }
.market-home-search-field span { font-size: 23px; line-height: 1; }
.market-home-search-field input, .market-home-search select { min-width: 0; width: 100%; height: 44px; border: 0; background: transparent; color: var(--fg); font-size: 14px; font-weight: 500; font-family: inherit; outline: none; }
.market-home-search select { border: 1px solid var(--border); border-radius: 13px; background: var(--card); padding: 0 10px; }
.market-home-search select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.market-home-place { display: block; width: 100%; min-height: 44px; margin: 0 0 22px; border: 0; border-radius: 13px; background: var(--accent); color: #fff; font-size: 15px; font-weight: 750; font-family: inherit; cursor: pointer; }
.market-home-section { margin: 0 0 24px; }
.market-home-section-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.market-home-section-head h2 { margin: 0; color: var(--fg); font-size: 20px; line-height: 1.2; font-weight: 800; letter-spacing: -.025em; }
.market-home-section-head button { border: 0; background: none; color: var(--accent); padding: 7px 0; font-size: 12px; font-weight: 700; font-family: inherit; cursor: pointer; white-space: nowrap; }
.market-home-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.market-home-grid .tile { min-width: 0; background: #fff; }
.market-home-grid .tile-photo-wrap { aspect-ratio: 1.25; }
.market-home-status { padding: 12px 4px; color: var(--fg-dim); font-size: 13px; }
.market-home-status:empty { display: none; }
.market-home-more { position: relative; flex: 0 0 auto; }
.market-home-more[open] { z-index: 6; }
.market-home-more summary { display: flex; align-items: center; gap: 7px; list-style: none; border: 1px solid var(--border); border-radius: 12px; padding: 9px 12px; background: var(--card); color: var(--fg); font-size: 12px; font-weight: 700; cursor: pointer; }
.market-home-more summary::-webkit-details-marker { display: none; }
.market-home-more summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.home-menu-icon { font-size: 17px; line-height: 1; }
.market-home-menu-content { position: absolute; top: calc(100% + 8px); right: 0; width: min(330px, calc(100vw - 32px)); max-height: min(70vh, 580px); overflow-y: auto; padding: 8px; border: 1px solid var(--border); border-radius: 16px; background: var(--card); box-shadow: 0 14px 38px rgba(16, 24, 40, .16); }
.home-menu-caption { padding: 7px 10px 5px; color: var(--fg-dim); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.home-menu-divider { height: 1px; margin: 6px 4px; background: var(--border); }
.market-home-menu-content .home-menu-link { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; min-height: 44px; margin: 0; padding: 10px 11px; border: 0; border-radius: 10px; background: transparent; box-shadow: none; color: var(--fg); font-family: inherit; font-size: 14px; font-weight: 600; line-height: 1.3; text-align: left; white-space: normal; cursor: pointer; }
.market-home-menu-content .home-menu-link:hover, .market-home-menu-content .home-menu-link:focus-visible { background: var(--active-soft); color: var(--accent); outline: none; }
.market-home-menu-content .home-menu-link:active { background: var(--active-soft); }
.home-menu-label { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.market-home-menu-content .quick-btn[data-quick="profile"] .quick-avatar,
.market-home-menu-content .quick-btn[data-quick="profile"] .quick-avatar-img,
.market-home-menu-content .quick-btn[data-quick="profile"] .quick-avatar-icon { width: 24px; height: 24px; border-radius: 7px; }
.home-menu-arrow { flex: 0 0 auto; color: var(--fg-dim); font-size: 20px; font-weight: 400; line-height: 1; }
.market-home-menu-content .home-chats-badge { vertical-align: middle; margin-left: 4px; }
@media (max-width: 680px) {
  .market-home-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .market-home-brand { font-size: 23px; }
  .market-home-section-head h2 { font-size: 18px; }
}
@media (max-width: 360px) {
  .market-home-search { grid-template-columns: minmax(0, 1fr) 112px; }
  .market-home-search-field { padding: 0 8px; }
  .market-home-search select { padding: 0 6px; font-size: 12px; }
}

/* Мобильная главная: счётчики под логотипом и устойчивые кнопки. */
body::before, body::after { display: none; }
.topbar { padding: 16px 16px 0; border-bottom: 0; }
.topbar .ad-banner { border-radius: 16px; padding-left: 0; box-shadow: none; animation: none; }
.topbar .ad-banner::after, .topbar .ad-banner-glow { display: none; }
.topbar .ad-banner-body { padding: 18px 20px; }
.home-view { padding-top: 22px; }
.market-home-head { align-items: center; margin-bottom: 4px; }
.market-home-brand { font-size: 26px; line-height: 1; font-weight: 850; letter-spacing: -.045em; }
.market-home-more summary { min-height: 44px; padding: 0 14px; border-radius: 12px; background: var(--bg); }
.home-view .home-stats-bar { justify-content: flex-start; flex-wrap: wrap; gap: 0 6px; margin: 0 0 18px; padding: 2px 0 0; font-size: 12px; line-height: 1.5; color: var(--fg-dim); }
.home-view .home-stats-clickable { display: inline-flex; align-items: center; gap: 4px; min-height: 28px; padding: 0; border: 0; border-radius: 4px; }
.home-view .home-stats-item b { font-weight: 650; font-variant-numeric: tabular-nums; }
.home-view .home-stats-divider { color: var(--fg-dim); opacity: .5; }
.market-home-search { gap: 10px; margin-bottom: 12px; }
.market-home-search-field { border-radius: 12px; }
.market-home-search-field input { margin: 0; padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none; height: 46px; font-size: 16px; }
.market-home-search select { height: 48px; border-radius: 12px; font-size: 14px; }
.market-home-place { display: flex; align-items: center; justify-content: center; width: 100%; min-height: 52px; margin: 0 0 26px; padding: 14px 20px; border-radius: 14px; font-size: 15px; font-weight: 650; line-height: 1.3; box-shadow: none; }
.market-home-section-head { margin-bottom: 12px; }
.market-home-section-head h2 { font-size: 20px; font-weight: 750; }
.market-home-grid { gap: 12px; }
.market-home-grid .tile { border-radius: 16px; box-shadow: none; background: var(--bg); animation: none; }
.market-home-grid .tile-photo-wrap { overflow: hidden; }
.market-home-menu-content { background: var(--bg); border-radius: 16px; box-shadow: 0 10px 30px rgba(16,24,40,.12); }
.home-view button, .home-view summary, .topbar .ad-banner { transform: none; animation: none; }
.home-view button:not(:disabled):hover, .home-view button:not(:disabled):active,
.home-view summary:hover, .home-view summary:active,
.topbar .ad-banner:hover, .topbar .ad-banner:active { transform: none; }
.home-view .quick-avatar, .home-view .quick-emoji, .home-view .home-tile-icon { animation: none; }
.home-view .quick-btn:active .quick-avatar, .home-view .quick-btn:active .quick-emoji { transform: none; }
.market-home-place:active, .market-home-more summary:active { opacity: .85; }
.market-home-place:focus-visible, .home-stats-clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (max-width: 350px) {
  .market-home-brand { font-size: 23px; }
  .market-home-more summary { padding: 0 11px; }
  .home-view .home-stats-bar { font-size: 11px; gap: 0 5px; }
  .market-home-section-head h2 { font-size: 18px; }
}

/* Сводка непосредственно под логотипом, без отдельного высокого ряда. */
.market-home-head { align-items: flex-start; margin-bottom: 14px; }
.market-home-identity { flex: 1 1 auto; min-width: 0; }
.market-home-identity .home-stats-bar { margin: 4px 0 0; padding: 0; gap: 0 5px; font-size: 11px; line-height: 1.4; }
.market-home-identity .home-stats-clickable { min-height: 18px; line-height: 1.4; }

/* Quiet, compact application footer with direct installer links. */
.app-downloads { margin-top: 24px; padding: 22px 16px 12px; border-top: 1px solid var(--border); text-align: center; }
.app-downloads-title { margin: 0 0 18px; color: var(--fg); font-size: 15px; font-weight: 650; line-height: 1.4; letter-spacing: -.015em; }
.app-downloads-platforms { display: flex; justify-content: center; align-items: center; gap: 14px; }
.app-download-link { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 56px; height: 56px; border: 1px solid var(--border); border-radius: 16px; background: var(--card); color: var(--fg-dim); text-decoration: none; -webkit-tap-highlight-color: transparent; transition: color .15s, border-color .15s, background .15s; }
.app-download-link svg { width: 25px; height: 25px; fill: currentColor; }
.app-download-link:hover { color: var(--accent); border-color: var(--accent); background: var(--active-soft); }
.app-download-link:active { opacity: .65; }
.app-download-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.app-download-chip { position: absolute; right: 4px; bottom: 3px; font-size: 8px; line-height: 11px; font-weight: 650; color: inherit; }
.app-downloads-copyright { margin-top: 16px; color: var(--fg-dim); font-size: 10px; font-weight: 550; line-height: 1.5; letter-spacing: .14em; }
@media (max-width: 350px) { .app-downloads { padding-left: 0; padding-right: 0; } .app-downloads-platforms { gap: 10px; } }
@media (prefers-reduced-motion: reduce) { .app-download-link { transition: none; } }


/* Private conversations: compact header and consistent, easy-to-scan rows. */
#messagesView { max-width: 620px; padding: 8px 12px calc(80px + var(--safe-b)); }
.messages-header { display: flex; align-items: center; gap: 12px; margin: 2px 0 18px; }
.messages-back { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; flex: 0 0 auto; border: 1px solid var(--border); border-radius: 13px; background: var(--card); color: var(--fg); font-size: 29px; line-height: 1; font-weight: 400; font-family: inherit; cursor: pointer; }
.messages-title { margin: 0; color: var(--fg); font-size: 20px; line-height: 1.25; font-weight: 750; letter-spacing: -.025em; }
.messages-list { gap: 8px; margin-bottom: 12px; }
.messages-list .dlg-swipe { margin: 0; border-radius: 16px; }
.messages-list .dlg-row { min-height: 68px; gap: 12px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 16px; background: var(--card); }
.messages-list .dlg-ava { width: 44px; height: 44px; }
.messages-list .dlg-row-name { font-size: 15px; font-weight: 700; }
.messages-list .dlg-row-at { font-size: 12px; }
.messages-list .dlg-row-last { margin-top: 4px; font-size: 13px; }
.messages-list .dlg-swipe-tip { margin: 12px 4px 4px; font-size: 12px; }
.market-dock button[data-market-nav="place"] { color: var(--pine); background: var(--pine-soft); }
.market-dock button[data-market-nav="place"] .market-dock-icon { background: var(--pine); }

.market-dock .home-chats-badge { position: absolute; top: 1px; left: calc(50% + 8px); min-width: 16px; height: 16px; padding: 0 4px; border-radius: 9px; font-size: 9px; line-height: 16px; }

.home-suppliers-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.home-supplier-card { display: flex; min-width: 0; min-height: 112px; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 10px 6px; border: 1px solid var(--border); border-radius: 16px; background: var(--card); color: var(--fg); font: inherit; cursor: pointer; text-align: center; }
.home-supplier-card:hover { border-color: var(--pine); background: var(--pine-soft); }
.home-supplier-avatar { display: inline-flex; width: 42px; height: 42px; flex: 0 0 auto; align-items: center; justify-content: center; overflow: hidden; border-radius: 50%; background: var(--pine-soft); color: var(--pine); font-size: 17px; font-weight: 700; }
.home-supplier-avatar img { width: 100%; height: 100%; object-fit: cover; }
.home-supplier-name { display: -webkit-box; max-width: 100%; overflow: hidden; color: var(--fg); font-size: 12px; font-weight: 700; line-height: 1.25; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.home-supplier-market { max-width: 100%; overflow: hidden; color: var(--fg-dim); font-size: 10px; line-height: 1.2; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 360px) { .home-suppliers-grid { gap: 7px; } .home-supplier-card { min-height: 104px; padding: 8px 4px; } .home-supplier-avatar { width: 38px; height: 38px; } }
