/* Каталог passon. Токены и раскладка по docs/handoff/README.md.
   Файл целиком вшивается в index.html при сборке (src/board.py).

   Два сквозных правила, от которых зависит остальное:
   1. Никаких left/right в раскладке, только логические свойства. Зеркальность
      для арабского включается атрибутом dir на корне, без правки CSS.
   2. Тёмная тема это отдельный набор значений, а не инверсия: чернильный
      акцент на тёмном фоне нечитаем и там светлеет. */

:root{
  --bg:#f4f2ee; --surface:#fffefb; --surface-muted:#eeebe4; --fill:#efece5;
  --border:rgba(43,38,30,.16); --border-soft:rgba(43,38,30,.10); --hairline:rgba(43,38,30,.08);
  /* --t3 было #7d7466: на --bg даёт 4,12:1 при норме AA 4,5:1. На этом токене
     держится вся периферия карточки (возраст, «/ мес», пересчёт валюты,
     счётчик фото, дисклеймер), замерено дизайн-ревью 07.08.2026 на живой
     странице. #6f6658 даёт ~5:1, тёплый тон не меняется. Тёмную тему не
     трогать: там #948c7c на #14120f норму уже проходит. */
  --t1:#2b2620; --t2:#5b5348; --t3:#6f6658;
  --accent:#26375c; --accent-strong:#1b2942; --accent-fill:#26375c; --on-accent:#fdfcf9;
  --rent:#26375c; --daily:#8a5a1a; --daily-tx:#7a5326; --sale:#2f5347; --wanted:#5b5348;
  --fresh:#26375c; --warn-tx:#7a5326; --danger:#8c3a2e;
  --wash-rent:rgba(38,55,92,.10); --wash-daily:rgba(122,83,38,.10);
  --wash-sale:rgba(47,83,71,.10); --wash-warn:rgba(122,83,38,.10);
  --wash-danger:rgba(140,58,46,.09);
  --scrim:rgba(43,38,30,.42);
  --shadow:0 1px 2px rgba(43,38,30,.04);
  --shadow-pop:0 18px 60px rgba(43,38,30,.30);
  /* Было 3px рамкой на 18% альфы: на --bg это ~1,37:1 при норме 3:1 (WCAG
     1.4.11/2.4.11), фактически кольца не было. Двойное кольцо: внутреннее
     на var(--bg) отделяет от фона под элементом, внешнее на var(--accent)
     несёт контраст. Оба токена уже теневые под тему, поэтому формула одна
     на обе темы и вниз в тёмный блок её дублировать не нужно отдельно. */
  --ring:0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
  --dur:.15s; --ease:cubic-bezier(.4,0,.2,1);
}
@media(prefers-color-scheme:dark){:root:not([data-theme=light]){
  --bg:#14120f; --surface:#1e1b16; --surface-muted:#1a1713; --fill:#241f19;
  --border:rgba(255,255,255,.10); --border-soft:rgba(255,255,255,.07); --hairline:rgba(255,255,255,.06);
  --t1:#f2ede4; --t2:#c3bbac; --t3:#948c7c;
  --accent:#a9bde8; --accent-strong:#a9bde8; --accent-fill:#3c5387;
  --rent:#a9bde8; --daily:#e0a94f; --daily-tx:#e0a94f; --sale:#7fb99f; --wanted:#c3bbac;
  --fresh:#a9bde8; --warn-tx:#e0a94f; --danger:#e08a7a;
  --wash-rent:rgba(169,189,232,.12); --wash-daily:rgba(224,169,79,.12);
  --wash-sale:rgba(127,185,159,.12); --wash-warn:rgba(224,169,79,.12);
  --wash-danger:rgba(224,138,122,.12);
  --scrim:rgba(0,0,0,.62);
  --shadow:none; --shadow-pop:0 18px 60px rgba(0,0,0,.55);
  /* --ring не переопределяется: формула в :root уже собрана из var(--bg) и
     var(--accent), а они здесь свои. Второе значение было идентично первому
     до правки контраста, дублировать нечего. */
}}
:root[data-theme=dark]{
  --bg:#14120f; --surface:#1e1b16; --surface-muted:#1a1713; --fill:#241f19;
  --border:rgba(255,255,255,.10); --border-soft:rgba(255,255,255,.07); --hairline:rgba(255,255,255,.06);
  --t1:#f2ede4; --t2:#c3bbac; --t3:#948c7c;
  --accent:#a9bde8; --accent-strong:#a9bde8; --accent-fill:#3c5387;
  --rent:#a9bde8; --daily:#e0a94f; --daily-tx:#e0a94f; --sale:#7fb99f; --wanted:#c3bbac;
  --fresh:#a9bde8; --warn-tx:#e0a94f; --danger:#e08a7a;
  --wash-rent:rgba(169,189,232,.12); --wash-daily:rgba(224,169,79,.12);
  --wash-sale:rgba(127,185,159,.12); --wash-warn:rgba(224,169,79,.12);
  --wash-danger:rgba(224,138,122,.12);
  --scrim:rgba(0,0,0,.62);
  --shadow:none; --shadow-pop:0 18px 60px rgba(0,0,0,.55);
  /* --ring не переопределяется, см. комментарий в блоке prefers-color-scheme выше. */
}

*{box-sizing:border-box}
html,body{overflow-x:hidden}
/* Системный стек закрывает латиницу и кириллицу без загрузки шрифтов. */
body{margin:0;background:var(--bg);color:var(--t1);
  font:15px/1.5 system-ui,-apple-system,"Segoe UI","Noto Sans",Arial,sans-serif;
  text-wrap:pretty}
/* Арабскому нужен более просторный интерлиньяж, чем кириллице. */
:root[dir=rtl] body{line-height:1.48}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}
a{color:inherit}
:focus-visible{outline:none;box-shadow:var(--ring);border-radius:8px}

/* --- Шапка (правка 17.08.2026: одна строка) --------------------------------
   Раньше было два ряда: замер 06.08 (когда добавлялась вторая полоса) прямо
   показывал, что в одну строку из 1345 px было занято 1305, свободных 40, и
   кнопка входа в аккаунт туда не помещалась. С тех пор в шапку добавились
   ещё вход и подача, так что в лоб одна строка не сходилась и подавно.

   Сходится она за счёт трёх вещей: поиск живёт на flex:1 и просто сжимается
   под то место, что осталось (было 709 px не потому что ему нужно столько,
   а потому что было куда расти); вкладки категорий переехали в тот же ряд,
   к началу, рядом с гео (тот же довод, что был у двух рядов: категория
   определяет выдачу и состав фильтров, поэтому она у начала строки, а не
   среди настроек показа в конце); группа языка/валюты/темы осталась той же
   тройкой кнопок, но валюта показывает только код (USD), а не «THB → USD».

   На телефоне разметка та же самая: строка переносится по .catbar (см.
   правило ниже), и получаются те же два ряда, что были у мобильного
   каркаса раньше, просто без второй копии разметки.

   Высота ряда(ов) зафиксирована в --chrome, но не руками: syncChrome() в
   app.js меряет .top после каждой перерисовки и держит переменную в
   актуальном состоянии, а не полагается на то, что кто-то не забудет
   поправить число при следующей правке отступов. */
.top{position:sticky;top:0;z-index:5;background:var(--surface);
  border-block-end:1px solid var(--border)}
.topbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px 10px;padding:8px 20px}
/* direction:ltr обязательно: логотип собран из двух span («pass» и «on»), и в
   RTL они менялись местами, давая «onpass». Название бренда не зеркалится
   ни в одном языке, это латинское слово, а не элемент раскладки. */
.logo{font-size:14px;font-weight:650;letter-spacing:.11em;text-transform:uppercase;
  white-space:nowrap;flex-shrink:0;display:inline-flex;align-items:center;
  direction:ltr}
.logo .on{color:var(--accent)}
.logo .dot{width:5px;height:5px;border-radius:50%;background:var(--daily);
  margin-inline-start:7px}
.geo{display:inline-flex;align-items:center;gap:8px;background:var(--fill);
  border:1px solid var(--border);border-radius:8px;padding:8px 11px;font-size:13px;
  white-space:nowrap;flex-shrink:0}
.geo b{font-weight:600;color:var(--t1)}
.geo .sep{width:1px;height:12px;background:var(--border)}
.geo .city{color:var(--t2)}
.geo .chev{font-size:10px;color:var(--t3)}
/* Меню выбора страны. В хендоффе кнопка страны в прототипе циклическая, а
   настоящее меню отнесено в блок 1d вне объёма. Пока стран было две, цикл
   работал; на десяти он стал неюзабельным, поэтому меню сделано здесь. */
.geowrap{position:relative;flex-shrink:0}
.menu{position:absolute;inset-inline-start:0;top:calc(100% + 6px);z-index:20;
  min-width:190px;max-height:60vh;overflow-y:auto;background:var(--surface);
  border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-pop);
  padding:5px}
.menu button{display:flex;align-items:center;gap:8px;width:100%;text-align:start;
  font-size:13px;padding:8px 10px;border-radius:7px;color:var(--t2)}
.menu button:hover{background:var(--fill);color:var(--t1)}
.menu button[aria-selected=true]{background:var(--accent-fill);color:var(--on-accent)}
.menu .n{margin-inline-start:auto;font-size:12px;opacity:.7;
  font-variant-numeric:tabular-nums}

/* Вкладки категорий. Вкладка с подчёркиванием, а не сегмент-переключатель:
   сегмент это выбор значения в равном ряду, вкладка это переход на другой
   раздел. Категории именно второе. Раньше полоса шла во всю ширину под
   двумя колонками; теперь это один из элементов строки шапки, flex-shrink:0
   (не сжимается, ужимать четыре подписи до нечитаемого незачем) и со своей
   прокруткой на случай узкого «десктопа» (планшет в альбомной ориентации),
   а не с переносом строки, который сломал бы прилегающую вёрстку. */
.catbar{flex-shrink:0;max-inline-size:46vw;overflow-x:auto;scrollbar-width:none}
.catbar::-webkit-scrollbar{display:none}
.cattabs{display:flex;gap:14px}
/* min-height:44px, а не больший padding: дизайн-ревью 07.08.2026 замерило
   34-41px по высоте на переключателях шапки при рекомендованном минимуме
   44px. inline-flex с центровкой держит текст по центру возросшей цели, не
   раздувая видимую плашку. */
.cattabs button{font-size:14px;font-weight:600;color:var(--t2);padding:9px 1px;
  min-height:44px;display:inline-flex;align-items:center;
  white-space:nowrap;border-bottom:2px solid transparent;
  transition:color var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.cattabs button:hover:not([disabled]){color:var(--t1)}
.cattabs button[aria-selected=true]{color:var(--t1);border-bottom-color:var(--accent)}
.cattabs button[disabled]{opacity:.45;cursor:not-allowed}

.search{flex:1;min-width:120px;background:var(--surface);color:var(--t1);
  border:1px solid var(--border);border-radius:8px;padding:9px 12px;font-size:13px;
  font-family:inherit}
.search:focus{outline:none;border-color:var(--accent);box-shadow:var(--ring)}

/* Настройки показа одной группой. Три отдельные кнопки весили в ряду столько
   же, сколько кнопка подачи, хотя это не действия, а состояние показа. */
.utils{display:inline-flex;align-items:center;background:var(--surface);
  border:1px solid var(--border);border-radius:8px;flex-shrink:0;overflow:hidden}
/* min-height:44px по той же причине, что у вкладок категорий: было 27-84
   на 34px, замерено дизайн-ревью 07.08.2026. */
.ubtn{font-size:12px;font-weight:600;padding:8px 10px;color:var(--t2);
  min-height:44px;display:inline-flex;align-items:center;
  white-space:nowrap;transition:background var(--dur) var(--ease),color var(--dur) var(--ease)}
.ubtn + .ubtn{border-inline-start:1px solid var(--border)}
.ubtn:hover{background:var(--fill);color:var(--accent-strong)}

.tbtn{font-size:12px;font-weight:600;background:var(--surface);border:1px solid var(--border);
  border-radius:8px;padding:8px 10px;color:var(--t2);white-space:nowrap;flex-shrink:0;
  transition:border-color var(--dur) var(--ease),color var(--dur) var(--ease)}
.tbtn:hover{border-color:rgba(38,55,92,.5);color:var(--accent-strong)}
.primary{background:var(--accent-fill);color:var(--on-accent);font-size:13px;
  font-weight:600;padding:9px 14px;border-radius:8px;white-space:nowrap;flex-shrink:0}
.primary:hover{background:var(--accent-strong)}
#filters{display:none}   /* видна только на телефоне, см. медиа-запрос ниже */

/* --- Раскладка ------------------------------------------------------------ */
/* Высота липкой шапки. Значение здесь только запасное, на случай если JS не
   выполнился: реальное число ставит syncChrome() в app.js после каждой
   перерисовки, читая offsetHeight у .top. До 17.08.2026 число было записано
   руками и разъехалось на 11 px с фактической высотой в ту же сторону, что
   и здесь могло случиться снова при следующей правке отступов. */
:root{--chrome:57px}
.wrap{display:grid;grid-template-columns:272px 1fr;align-items:start}
.side{border-inline-end:1px solid var(--border);padding:14px 16px 0;
  position:sticky;top:var(--chrome);max-height:calc(100vh - var(--chrome));
  display:flex;flex-direction:column;min-height:0}
/* Маска на нижних 24px и прокрутка лежат на #panel, а не на .side: рельса
   под баннер (#adrail) идёт следом за прокручиваемой панелью и должна
   оставаться на виду целиком, а не гаснуть под тем же градиентом, что скрывал
   обрезанный список фильтров (пример из дизайн-ревью 07.08.2026, экран
   540px высоты). */
#panel{flex:1 1 auto;min-height:0;overflow-y:auto;padding-bottom:24px;
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 24px),transparent 100%);
  mask-image:linear-gradient(to bottom,#000 calc(100% - 24px),transparent 100%)}
.results{padding:14px 20px 26px;min-height:660px;min-width:0}

/* --- Панель фильтров -------------------------------------------------------
   Раньше все группы стояли раскрытыми одним столбцом: у жилья это 1826 px
   содержимого при 843 px экрана, то есть вторая полоса прокрутки поверх уже
   прокручиваемой выдачи, и никакой иерархии между «Городом» и «Источником».

   Теперь группа это раскрывашка. Открыты по умолчанию только те, без которых
   каталог не работает (город, тип сделки, цена), остальные свёрнуты в строку
   заголовка. Свёрнутая группа не прячет состояние: выбранное значение стоит
   в самом заголовке, поэтому фильтр, о котором забыли, видно не открывая.
   Состав и порядок групп задаётся таблицей GROUPS в app.js, по строке на
   категорию: новый фильтр не ломает раскладку, потому что добавляет одну
   строку заголовка, а не колонку контролов. */
.phead{display:flex;align-items:baseline;gap:8px;margin:0 0 6px}
.gtitle{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;
  color:var(--t3)}
.reset{margin-inline-start:auto;font-size:12px;color:var(--accent-strong);
  text-decoration:underline;padding:0}

.group{border-top:1px solid var(--hairline)}
.group:first-child{border-top:none}
.ghead{display:flex;align-items:center;gap:8px;width:100%;text-align:start;
  padding:11px 0}
.ghead:hover .gtitle{color:var(--t2)}
/* Значение свёрнутой группы. Акцентом, потому что это единственный след
   активного фильтра, когда группа закрыта. */
.gval{font-size:12px;color:var(--accent-strong);min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.gchev{margin-inline-start:auto;font-size:11px;line-height:1;color:var(--t3);
  flex-shrink:0;transition:transform var(--dur) var(--ease)}
.ghead:hover .gchev{color:var(--t2)}
.ghead[aria-expanded=true] .gchev{transform:rotate(180deg)}
.gbody{padding-bottom:14px}
.chips{display:flex;flex-wrap:wrap;gap:6px}
.chip{border:1px solid var(--border);background:var(--surface);color:var(--t2);
  border-radius:20px;padding:7px 11px;font-size:12px;white-space:nowrap;
  transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease)}
.chip .n{opacity:.65;margin-inline-start:4px}
.chip[aria-pressed=true]{background:var(--accent-fill);color:var(--on-accent);
  border-color:var(--accent-fill)}
.chip[aria-pressed=true] .n{opacity:.8}
.rows{display:flex;flex-direction:column;gap:5px}
/* Подпись переносится, а не обрезается: запас под немецкий и английский,
   где та же фраза длиннее на 40%. */
.row{display:flex;align-items:center;gap:8px;border:1px solid var(--border);
  background:var(--surface);color:var(--t2);border-radius:8px;padding:9px 11px;
  font-size:13px;line-height:1.35;text-align:start;width:100%}
.row .lbl{flex:1;min-width:0}
.row .n{margin-inline-start:auto;flex-shrink:0;font-size:12px;opacity:.7;
  font-variant-numeric:tabular-nums}
.row .mk{flex-shrink:0}
.row[aria-pressed=true]{background:var(--accent-fill);color:var(--on-accent);
  border-color:var(--accent-fill)}
.row[aria-pressed=true] .mk{background:var(--on-accent);border-color:var(--on-accent)}
.row[aria-pressed=true] .n{opacity:.85}
.quad{display:flex;gap:5px}
.quad .row{flex:1;justify-content:center;text-align:center;padding:9px 4px}
.prices{display:flex;align-items:center;gap:6px}
.prices input{flex:1;min-width:0;background:var(--surface);color:var(--t1);
  border:1px solid var(--border);border-radius:8px;padding:8px 10px;font-size:13px;
  font-family:inherit;font-variant-numeric:tabular-nums}
.prices input:focus{outline:none;border-color:var(--accent);box-shadow:var(--ring)}
.prices .dash{color:var(--t3);flex-shrink:0}
.pnote{font-size:11px;color:var(--t3);line-height:1.4;margin-top:6px}

/* Скрыто визуально, но не от вспомогательных технологий: единственный h1
   страницы, меняется вместе с категорией и страной. Ни один пиксель макета
   не задет, поэтому не хендофф и не дизайн-ревью решают, как это выглядит:
   решает то, что оно не должно выглядеть никак. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* --- Строка результатов --------------------------------------------------- */
.rbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px}
.found{font-size:14px;color:var(--t2);font-variant-numeric:tabular-nums;flex-shrink:0}
/* Было продублировано в подвале каждой карточки (1194 раза тем же 11px, что
   и различающие данные, замерено дизайн-ревью). Правило одно на всю выдачу,
   поэтому и место ему одно, рядом со счётчиком.

   До 17.08.2026 строка была flex-basis:100%, то есть заметка всегда падала
   на вторую строку .rbar, даже когда в первой было пусто и фильтров не
   стояло. Правило по-прежнему одно на выдачу; меняется место, а не факт.
   На десктопе делит строку со счётчиком, если помещается (flex:1 1 220px,
   а не 100%). На телефоне строки не хватает почти никогда, поэтому там
   показывается второй экземпляр под выдачей — см. .rnote.foot и медиа-блок
   ниже, разметка у обоих одна и та же (data-t="contacts" дважды, тот же
   приём, что уже есть у «Фильтры»). */
.rnote{font-size:11px;color:var(--t3);line-height:1.4;flex:1 1 220px;min-width:0}
.rnote.foot{display:none}
.acts{display:flex;gap:6px;flex-wrap:wrap;min-width:0}
.act{display:inline-flex;align-items:center;gap:6px;font-size:12px;background:var(--fill);
  border:1px solid var(--border-soft);border-radius:20px;padding:5px 10px;color:var(--t2)}
.act .x{font-size:11px;opacity:.6}
.act:hover{border-color:rgba(38,55,92,.45);color:var(--accent-strong)}
.rtools{margin-inline-start:auto;display:flex;gap:6px;flex-shrink:0}

/* --- Сетка карточек -------------------------------------------------------
   «Список» это одна колонка, «Сетка» это три. Ровно так в прототипе хендоффа:
   dCols = density === 'grid' ? 'repeat(3, minmax(0,1fr))' : 'minmax(0,1fr)'.
   Было repeat(auto-fill,minmax(340px,1fr)): при колонке выдачи в 1033 px это
   само раскладывалось в те же три колонки, что и «Сетка», и переключатель
   не делал ничего. */
.grid{display:grid;gap:12px;grid-template-columns:minmax(0,1fr)}
.grid.dense{grid-template-columns:repeat(3,minmax(0,1fr))}
/* Место в потоке карточек под баннер: #grid.innerHTML собирается заново на
   каждый ввод (см. app.js render()), поэтому баннер это часть той же
   строки, а не элемент, дописанный отдельно и стираемый первым же кликом. */
.adfeed{grid-column:1/-1}

/* --- Баннеры (config.yaml -> ads, пока пуст) --------------------------------
   Свои, без внешней сети и счётчиков (см. docs/legal/privacy). Контейнеры
   всегда в разметке; пока инвентаря нет, они пустые и :empty{display:none}
   схлопывает их до нуля — непроданное место не должно стоить ни одного
   пикселя выдачи. */
.adslot{display:block;position:relative;overflow:hidden;border-radius:10px;
  border:1px solid var(--border-soft);background:var(--surface-muted);
  text-decoration:none}
.adslot img{inline-size:100%;block-size:100%;object-fit:cover;display:block}
.adlabel{position:absolute;inset-block-start:6px;inset-inline-start:6px;
  font:700 10px/1 system-ui,sans-serif;letter-spacing:.07em;text-transform:uppercase;
  color:var(--on-accent);background:rgba(43,38,30,.72);padding:4px 7px;border-radius:5px}
#adtop:empty,#adbottom:empty,#adrail:empty,.adfeed:empty{display:none}
#adtop{margin-bottom:10px}
#adtop .adslot{aspect-ratio:970/90}
#adbottom{margin-top:16px}
#adbottom .adslot{aspect-ratio:970/250}
#adrail{margin-top:12px}
#adrail .adslot{aspect-ratio:6/5}
.adfeed .adslot{aspect-ratio:2/1;max-height:200px;margin-inline:auto}
@media(max-width:1100px){#adrail{display:none}}
@media(max-width:1100px){.grid.dense{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* --- Карточка, вариант 1c -------------------------------------------------
   Единая сетка полей для обоих типов. Различие несут поверхность, метка типа
   и подвал. Агрегированное: приглушённый фон, тише граница, без тени. */
/* min-width:0 обязательно: без него длинное слово из поста (ссылка, адрес без
   пробелов) задирает min-content колонки, и карточка вылезает за сетку. На
   телефоне это давало 506 px карточки при экране 390 px, незаметно, потому что
   overflow-x на body срезает результат. */
/* content-visibility:auto пропускает layout/paint для карточек вне экрана:
   на 1194 объявлениях это 22 237 узлов DOM разом, замерено дизайн-ревью
   07.08.2026. contain-intrinsic-size даёт браузеру плейсхолдер высоты до
   первого реального рендера; auto запоминает настоящую высоту после него,
   поэтому неточность оценки не копится. Без переписывания на виртуальный
   список: один блок правил, поведение то же самое. */
/* Было 9px везде: семь смысловых блоков (тип сделки, заголовок, локация,
   цена, фрагмент, происхождение) читались одним темпом, без пауз на смену
   темы. Дизайн-ревью 07.08.2026: 6px внутри «объекта» (что это), 14px перед
   фрагментом и перед подвалом (откуда это). margin-block-start на самих
   .cfrag/.hr, а не второе значение gap: во flex-контейнере gap один на все
   пары, а какие блоки покажутся, зависит от данных. */
.card{border-radius:10px;padding:15px;display:flex;flex-direction:column;gap:6px;
  min-width:0;cursor:pointer;transition:border-color var(--dur) var(--ease);
  content-visibility:auto;contain-intrinsic-size:auto 380px}
.card.agg{background:var(--surface-muted);border:1px solid var(--border-soft)}
.card.usr{background:var(--surface);border:1px solid var(--border);box-shadow:var(--shadow)}
.card:hover{border-color:rgba(38,55,92,.45)}
.crow{display:flex;align-items:center;gap:8px;min-width:0}
.deal{display:inline-flex;align-items:center;gap:6px;font-size:10px;font-weight:700;
  letter-spacing:.07em;text-transform:uppercase;padding:4px 8px;border-radius:5px;
  white-space:nowrap}
/* Форма маркера это второй носитель смысла после подписи: экран должен
   оставаться читаемым при дальтонизме, цвет здесь последний по важности.
   Было 7×7px: дизайн-ревью 07.08.2026 замерило, что на таком размере квадрат
   и ромб отличаются на два-три пикселя силуэта, сглаживание съедает и их,
   то есть канал фактически не нёс сигнала. 10×10px плюс разная заливка
   (сплошная / контур) там, где формы уже мало. */
.mk{width:10px;height:10px;display:block;flex-shrink:0}
.d-rent{color:var(--accent-strong);background:var(--wash-rent)}
.d-rent .mk{background:var(--rent);border-radius:0}
.d-daily{color:var(--daily-tx);background:var(--wash-daily)}
.d-daily .mk{background:var(--daily);border-radius:50%}
.d-sale{color:var(--sale);background:var(--wash-sale)}
/* Было заливкой, как у аренды: два предложения читались одной формой.
   Контур без заливки отличим от обеих сплошных даже при потере цвета. */
.d-sale .mk{background:transparent;border:2px solid var(--sale);transform:rotate(45deg)}
.d-wanted{color:var(--t2);background:var(--fill)}
/* Спроса в хендоффе нет: дизайнер работал с тремя типами сделки и задал три
   формы. Четвёртая не может повторять чужую, иначе форма перестаёт различать.
   Кольцо отличимо от всех сплошных и читается как запрос, а не предложение. */
.d-wanted .mk{background:transparent;border:2px solid var(--wanted);border-radius:50%}
/* Значок агентства/компании. Место зарезервировано сейчас, наполнение
   (agency_id/name на listings_public) отдельной миграцией: см. план кабинета
   агентств. Тише «3 источника» (.pill): та подпись подтверждает данные, эта
   просто называет автора. Ширина ограничена и текст обрезается — .crow не
   переносится, и длинное название компании выдавило бы возраст за край
   карточки на телефоне. */
.org{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;
  color:var(--t2);background:var(--fill);border:1px solid var(--border-soft);
  border-radius:20px;padding:2px 8px;max-inline-size:140px;min-inline-size:0;
  flex-shrink:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  text-decoration:none}
.org:hover{border-color:var(--accent)}
.org .orglogo{inline-size:14px;block-size:14px;border-radius:3px;object-fit:cover;flex-shrink:0}
.deal{flex-shrink:0}
.age{margin-inline-start:auto;display:inline-flex;align-items:center;gap:5px;
  font-size:12px;color:var(--t3);white-space:nowrap;flex-shrink:0}
.age.new{color:var(--fresh);font-weight:600}
/* Возраст сам по себе уже подозрителен, поэтому у протухших он гасится, а
   словесная пометка ставится отдельной строкой: цвет один смысл не несёт. */
.age.stale{color:var(--warn-tx)}
.stalemark{font-size:11px;font-weight:600;color:var(--warn-tx);
  background:var(--wash-warn);border-radius:5px;padding:3px 7px;align-self:flex-start}
.age.new i{width:6px;height:6px;border-radius:50%;background:var(--fresh);display:block}
/* .ctitle сидит на <h3>, margin:0 обязателен: иначе браузерный отступ
   заголовка (~1em) ломает gap:9px внутри карточки. Кнопка внутри открывает
   ту же карточку, что и клик по ней, но даёт фокус и клавиатуру: раньше
   карточка была недостижима с Tab, замерено дизайн-ревью: 39 фокусируемых
   элементов на 1194 объявления. */
.ctitle{font-size:16px;font-weight:640;color:var(--t1);line-height:1.3;
  overflow-wrap:break-word;margin:0}
.ctitlebtn{display:block;width:100%;text-align:start;font:inherit;color:inherit;padding:0}
.cplace{font-size:13px;color:var(--t2)}
.cprice{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}
.amt{font-size:21px;font-weight:660;color:var(--t1);font-variant-numeric:tabular-nums;
  letter-spacing:-.015em}
.per{font-size:13px;color:var(--t3)}
.alt{font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}
.noprice{font-size:13px;font-weight:600;color:var(--warn-tx)}
.crange{font-size:12px;color:var(--t3)}
.cfrag{font-size:13px;line-height:1.55;color:var(--t2);overflow-wrap:break-word;
  margin-block-start:8px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.hr{height:1px;background:var(--hairline);margin-block-start:8px}
.cfoot{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--t3);flex-wrap:wrap}
.src{color:var(--t2)}
.pill{font-size:11px;font-weight:600;color:var(--accent-strong);background:var(--wash-rent);
  padding:2px 7px;border-radius:20px;white-space:nowrap}
/* Сколько снимков в исходном посте. Раньше это была подпись внутри заглушки
   в 132 px, то есть самый крупный элемент карточки ради одного числа. Факт
   тот же, вес соразмерный: строка в подвале рядом с названием канала. */
.pcount{font-size:11px;color:var(--t3);white-space:nowrap}
.btn2{margin-inline-start:auto;font-size:12px;font-weight:600;color:var(--accent-strong);
  border:1px solid rgba(38,55,92,.35);border-radius:8px;padding:7px 11px;
  text-decoration:none;white-space:nowrap;flex-shrink:0;background:transparent}
.btn2:hover{border-color:var(--accent);background:var(--wash-rent)}
/* Отсутствие контактов это осознанное решение, а не недоделка. Говорим прямо. */
.cnote{font-size:11px;color:var(--t3);line-height:1.4}

/* Место под фото. Реальных изображений у агрегированных объявлений нет
   практически никогда, поэтому заглушка объясняет пустоту текстом, а не
   маскирует её серым прямоугольником. */
.shot{height:132px;border-radius:8px;border:1px solid var(--border-soft);
  display:flex;align-items:center;justify-content:center;text-align:center;
  font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--t3);
  padding:8px;
  background:repeating-linear-gradient(45deg,var(--fill),var(--fill) 6px,
             var(--surface-muted) 6px,var(--surface-muted) 12px)}

/* Настоящее фото. Заглушка выше остаётся для случая, когда копии ещё нет:
   пустое место объясняется словами, а не серым прямоугольником. */
.shot.has{padding:0;overflow:hidden;position:relative;background:var(--surface-muted)}
.shot.has img{width:100%;height:100%;object-fit:cover;display:block}
.shotn{position:absolute;inset-block-end:6px;inset-inline-end:6px;
  font:600 11px/1 system-ui,sans-serif;color:var(--on-accent);
  background:rgba(43,38,30,.72);padding:4px 7px;border-radius:20px}
/* Полоса фото в развёрнутой карточке: горизонтальная прокрутка вместо сетки,
   потому что снимков бывает и десять, а вертикаль в оверлее дороже. */
.strip.has{display:flex;gap:6px;overflow-x:auto;height:140px;border-radius:10px;
  scrollbar-width:thin}
.strip.has img{height:100%;width:auto;border-radius:8px;object-fit:cover;flex-shrink:0}

/* --- Состояния выдачи (блок 1h) ------------------------------------------- */
/* Скелет повторяет структуру настоящей карточки (плашка сделки, заголовок,
   локация, крупная цена, две строки фрагмента, подвал), а не шесть полосок
   одной ширины: дизайн-ревью 07.08.2026 замерило, что старый скелет другой
   высоты, чем реальная карточка, и выдача дёргается при подстановке данных.
   Без блюров и теней: на странице бывает до 1500 карточек. */
.sk{background:var(--surface-muted);border:1px solid var(--border-soft);border-radius:10px;
  padding:15px;display:flex;flex-direction:column;gap:6px;animation:pulse 1.6s var(--ease) infinite}
.sk i{display:block;background:var(--fill);border-radius:4px}
.skrow{display:flex;align-items:center;gap:8px}
.skrow i.badge{width:88px;height:20px;border-radius:5px}
.skrow i.age{margin-inline-start:auto;width:52px;height:14px}
.sk i.title{width:68%;height:20px;margin-block-start:2px}
.sk i.place{width:46%;height:14px}
.sk i.price{width:38%;height:24px;margin-block-start:2px}
.sk i.line{width:100%;height:16px;margin-block-start:8px}
.sk i.line.w80{width:80%;margin-block-start:4px}
.sk i.foot{width:56%;height:12px;margin-block-start:8px}
@keyframes pulse{0%,100%{opacity:.45}50%{opacity:.9}}
@media(prefers-reduced-motion:reduce){.sk{animation:none;opacity:.6}}

.state{border:1px dashed var(--border);border-radius:12px;padding:34px 24px;
  text-align:center;color:var(--t2);grid-column:1/-1}
.state h2{margin:0 0 8px;font-size:17px;font-weight:640;color:var(--t1)}
.state p{margin:0 0 14px;font-size:13px;line-height:1.55}
.state .row2{display:flex;gap:8px;justify-content:center;flex-wrap:wrap}
.state.err{border-style:solid;border-color:var(--danger);background:var(--wash-danger)}
.plate{border:1px solid var(--border);background:var(--wash-warn);border-radius:10px;
  padding:11px 14px;font-size:13px;color:var(--warn-tx);margin-bottom:14px;line-height:1.5}

/* --- Развёрнутая карточка (оверлей) --------------------------------------- */
.scrim{position:fixed;inset:0;background:var(--scrim);z-index:8;overflow-y:auto;
  padding:44px 16px 40px;animation:fade .2s var(--ease)}
.pop{max-width:760px;margin:0 auto;background:var(--surface);border-radius:14px;
  box-shadow:var(--shadow-pop);border:1px solid var(--border);
  animation:rise .22s var(--ease)}
@keyframes fade{from{opacity:0}to{opacity:1}}
@keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.scrim,.pop{animation:none}}
.pophead{position:sticky;top:0;background:var(--surface);border-bottom:1px solid var(--hairline);
  border-radius:14px 14px 0 0;padding:13px 18px;display:flex;align-items:center;gap:10px;z-index:1}
.close{margin-inline-start:auto;width:32px;height:32px;border-radius:8px;
  border:1px solid var(--border);color:var(--t2);flex-shrink:0;line-height:1}
.close:hover{border-color:var(--accent);color:var(--accent-strong)}
.popbody{padding:18px;display:flex;flex-direction:column;gap:18px}
.popttl{font-size:24px;font-weight:650;line-height:1.25;margin:0 0 6px}
.popplace{font-size:14px;color:var(--t2)}
.popprice{font-size:28px;font-weight:660;font-variant-numeric:tabular-nums;
  letter-spacing:-.02em;margin-top:10px;display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.popprice .per{font-size:15px}
.warnbox{border:1px solid var(--border);background:var(--wash-warn);color:var(--warn-tx);
  border-radius:10px;padding:11px 14px;font-size:13px;margin-top:10px}
.rangebox{background:var(--surface-muted);border-radius:10px;padding:11px 14px;
  font-size:13px;color:var(--t2);margin-top:10px;line-height:1.5}
.strip{height:140px}
/* gap:1px на фоне границы даёт хайрлайны без вложенных бордеров. */
.tbl{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--border-soft);
  border:1px solid var(--border-soft);border-radius:10px;overflow:hidden}
.tbl div{background:var(--surface);padding:11px 13px}
.tbl dt{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--t3);margin:0 0 3px}
.tbl dd{margin:0;font-size:14px;color:var(--t1)}
.raw{white-space:pre-line;line-height:1.7;background:var(--surface-muted);border-radius:10px;
  padding:14px 16px;font-size:14px;color:var(--t2);overflow-wrap:anywhere}
.srcbox{border:1px solid var(--border-soft);border-radius:10px;padding:14px 16px;
  display:flex;flex-direction:column;gap:10px}
.srcbox h3{margin:0;font-size:12px;font-weight:700;text-transform:uppercase;
  letter-spacing:.07em;color:var(--t3)}
.srcline{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--t2);flex-wrap:wrap}

/* --- Телефон (правка 17.08.2026) -------------------------------------------
   До этой правки телефон и десктоп рисовались из ДВУХ разных кусков
   разметки (.top и отдельный .mtop), с двумя копиями логотипа, гео, языка,
   темы и вкладок категорий и с двойной проводкой обработчиков в app.js.
   Теперь разметка одна: .topbar это флекс-строка, и на телефоне она просто
   переносится по .catbar{flex-basis:100%}, давая те же два ряда, что были у
   мобильного каркаса раньше, без второй копии.

   Поиск, подача и вход на телефоне по-прежнему не показываются — это не
   регресс этой правки, так было и в прежнем .mtop, который их не содержал
   вовсе. Валюта на телефоне, наоборот, появляется впервые: раньше кнопки
   #cur не было в мобильной разметке, хотя на десктопе валюту переключать
   можно было всегда, и это выглядело недосмотром, а не решением. */
@media(max-width:880px){
  /* #post и .acctwrap, а не .primary/.acctwrap как классы: ниже в этом же
     блоке есть .btn2,.primary{display:inline-flex} для кнопок внутри
     развёрнутой карточки, и он той же специфичности, но идёт позже —
     перебивал бы обычный класс. .acctwrap отдельно перебивает ещё и
     account.css:35, где display:flex стоит без медиа-запроса вовсе
     (верно для десктопа, но там нет условия «кроме телефона»); .topbar
     .acctwrap специфичнее одиночного класса и потому выигрывает независимо
     от порядка файлов при сборке. */
  .search,#post,.topbar .acctwrap{display:none}
  .top{border-block-end:none}
  .topbar{padding:8px 14px;gap:8px}
  .logo{font-size:13px}
  .geowrap{margin-inline-start:auto}
  .geo{padding:7px 8px;font-size:12px;max-inline-size:38vw}
  .geo b,.geo .city{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .ubtn{min-height:40px;padding:8px 7px}
  /* Вкладки категорий переносятся на свою строку и растягиваются на всю
     ширину: верхний уровень навигации, ему не пристало жаться к левому
     краю только потому, что он оказался вторым рядом, а не первым. */
  .catbar{flex-basis:100%;max-inline-size:none;order:2;
    border-block-start:1px solid var(--border);padding-block-start:2px}
  .cattabs{gap:0}
  .cattabs button{flex:1;justify-content:center;padding:10px 4px;font-size:13px}
  .wrap{grid-template-columns:1fr}
  .side{display:none}
  .results{padding:12px 14px 30px;min-height:0}
  .grid,.grid.dense{grid-template-columns:minmax(0,1fr)}
  .ctitle{font-size:15px}
  .amt{font-size:19px}
  .btn2,.primary{min-height:40px;display:inline-flex;align-items:center}
  /* Кнопка «Фильтры N» встаёт туда, где раньше была отдельная 54px-полоса
     .mchips: там было шесть кнопок с одним и тем же data-sheet="1"
     (открывали одну и ту же шторку), теперь одна, без потери действий. */
  #filters{display:inline-flex;align-items:center;gap:4px}
  .rbar .tbtn{min-height:36px}
  /* Заметке про контакты не хватает места в общей строке почти никогда:
     переезжает под выдачу вторым экземпляром той же подписи. */
  .rbar .rnote{display:none}
  .rnote.foot{display:block;margin-block-start:18px}
  /* Развёрнутая карточка на телефоне это не оверлей, а полноэкранный слой,
     и он лежит выше шторки фильтров. */
  .scrim{padding:0;background:var(--surface)}
  .pop{max-width:none;border-radius:0;border:none;box-shadow:none;min-height:100vh;
    padding-bottom:76px}
  .pophead{border-radius:0}
  .popttl{font-size:20px}
  .popprice{font-size:24px}
  .tbl{grid-template-columns:1fr}
  .popfoot{position:fixed;inset-inline:0;bottom:0;background:var(--surface);
    border-top:1px solid var(--border);padding:12px 14px;display:flex;gap:10px;z-index:9}
  .popfoot .btn2{margin:0;flex:1;justify-content:center;min-height:50px;font-size:14px}
  .fav{width:50px;height:50px;border-radius:10px;border:1px solid var(--border);
    flex-shrink:0;font-size:18px;color:var(--t2)}
  #adtop{display:none}   /* см. риск №3 плана: не выталкивать первую карточку */
  #adbottom .adslot{aspect-ratio:6/5}
  .adfeed .adslot{max-height:180px}
}
.popfoot{display:none}
@media(max-width:880px){.popfoot{display:flex}}

/* --- Шторка фильтров (телефон) -------------------------------------------- */
.sheetwrap{position:fixed;inset:0;z-index:6;display:none}
.sheetwrap.open{display:block}
.sheetbg{position:absolute;inset:0;background:var(--scrim);animation:fade .2s var(--ease)}
.sheet{position:absolute;inset-inline:0;bottom:0;max-height:88vh;background:var(--surface);
  border-radius:16px 16px 0 0;display:flex;flex-direction:column;
  animation:up .24s var(--ease)}
@keyframes up{from{transform:translateY(100%)}to{transform:none}}
@media(prefers-reduced-motion:reduce){.sheet,.sheetbg{animation:none}}
.sheethead{position:sticky;top:0;background:var(--surface);border-bottom:1px solid var(--hairline);
  border-radius:16px 16px 0 0;padding:14px 16px;display:flex;align-items:center;gap:10px}
.sheethead h2{margin:0;font-size:15px;font-weight:640}
.sheetbody{overflow-y:auto;padding:16px;flex:1}
.sheetfoot{border-top:1px solid var(--hairline);padding:12px 16px;background:var(--surface)}
.sheetfoot .primary{width:100%;justify-content:center;min-height:48px;display:inline-flex;
  align-items:center;font-size:14px}

/* Правило для очень узких экранов, державшее .rtools в 100% ширины, снято
   17.08.2026: .tbtn на телефоне теперь 36px и без #filters/#found/.rtools
   в одну строку укладываются вплоть до 360px (проверено headless-хромом,
   см. верификацию правки шапки). Если на более узком экране появится
   переполнение, чинить по свежему замеру, а не возвращать старое правило
   вслепую поверх новой раскладки. */

/* --- Текст страницы -------------------------------------------------------
   Блок, который собирает src/seo.py: заголовок, числа по району, перелинковка.
   Он лежит в разметке и виден без исполнения скрипта: этим страница и
   отличается от прежней витрины, где краулеру было видно сорок слов.

   Скрипт прячет блок целиком (атрибут hidden), как только человек сменил
   фильтр: числа описывают ту выдачу, под которую страница собрана, и к
   отфильтрованной уже не относятся. */
/* Верх страницы отвечает поисковику и объясняет место человеку, но занимать
   он должен столько, чтобы под ним в первый экран попадало объявление. До
   правки 16.08.2026 на главной было шесть рядов плашек со странами и
   городами, и первая карточка уезжала за нижний край. Плашки уехали в
   свёрнутый блок (см. links_block в seo.py), а здесь ужат сам ритм. */
.seo{margin-block-end:10px;max-inline-size:78ch}
.crumbs{font-size:12px;color:var(--t3);margin-block-end:4px}
.crumbs a{color:inherit}
.crumbs span{color:var(--t2)}
.seo h1{font-size:21px;line-height:1.25;font-weight:660;margin:0 0 4px}
.seo .lead{font-size:13px;line-height:1.55;color:var(--t2);margin:0 0 10px}
/* Страница компании (seo.agency_page_html): те же отступы и цвет, что у
   .lead, ниже него — контакты и счётчик объявлений, не основной текст. */
.seo .agycontacts,.seo .agycount{font-size:13px;line-height:1.55;color:var(--t2);margin:0 0 8px}
.seo .agycontacts a{color:var(--accent-strong)}
/* На телефоне лид на страницах разделов бывает 6-8 строк (шаблон описывает
   и город, и категорию, и сделку). Зажимаем до трёх с раскрытием по клику:
   текст остаётся в разметке (clamp, не hidden), краулеру видно всё как
   раньше, лишние строки просто не занимают экран до явного интереса. */
@media(max-width:880px){
  .seo .lead{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;
    overflow:hidden;cursor:pointer}
  .seo.leadopen .lead{-webkit-line-clamp:unset;overflow:visible;cursor:auto}
}
table.agg{border-collapse:collapse;inline-size:100%;max-inline-size:520px;
  font-size:13px;margin:0}
table.agg caption{text-align:start;color:var(--t3);font-size:12px;
  text-transform:uppercase;letter-spacing:.04em;padding-block-end:6px}
table.agg th{text-align:start;font-weight:600;color:var(--t3);font-size:12px;
  border-block-end:1px solid var(--border);padding:5px 10px 5px 0}
table.agg td{padding:5px 10px 5px 0;border-block-end:1px solid var(--hairline);color:var(--t2)}
table.agg td:first-child{color:var(--t1)}
/* Разбивка по спальням/типам жилья: на страницах разделов это одна-две
   таблицы, до 439 px на десктопе и больше на телефоне (первая колонка
   переносится) — крупнейший блок над выдачей, который до этой правки не
   получал сворачивания, хотя списки ссылок ниже получили его ещё 16.08. */
details.aggwrap{margin:0 0 14px}
details.aggwrap > summary{font-size:12px;text-transform:uppercase;
  letter-spacing:.04em;color:var(--t3);font-weight:600;cursor:pointer;
  padding-block:2px;list-style-position:inside}
details.aggwrap > summary:hover{color:var(--t1)}
details.aggwrap[open] > summary{margin-block-end:8px}
details.aggwrap > summary .n{color:var(--t3);margin-inline-start:6px;font-weight:400}
.seolinks{margin:0 0 8px}
.seolinks h2{font-size:12px;text-transform:uppercase;letter-spacing:.04em;
  color:var(--t3);font-weight:600;margin:0 0 6px}
.seolinks ul{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}
.seolinks li{background:var(--fill);border:1px solid var(--border-soft);
  border-radius:999px;padding:4px 11px;font-size:13px}
.seolinks a{color:var(--t1);text-decoration:none}
.seolinks a:hover{text-decoration:underline}
.seolinks .n{color:var(--t3);margin-inline-start:5px}
/* На телефоне пилюли переносятся в несколько рядов даже свёрнутым списком
   из шести. Горизонтальная прокрутка в одну строку дешевле по высоте, чем
   перенос, а список короткий (порог LINKS_INLINE_MAX=6), пролистать легко. */
@media(max-width:880px){
  .seolinks ul{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;padding-block-end:2px}
  .seolinks ul::-webkit-scrollbar{display:none}
  .seolinks li{flex-shrink:0}
}
/* Свёрнутый список ссылок: строка-заголовок с числом внутри, раскрывается по
   клику. Выглядит как заголовок соседнего развёрнутого блока, чтобы список
   из шести стран и список из двадцати одного города читались как одно и то
   же, отличаясь только тем, помещаются они в строку или нет. */
details.seolinks > summary{font-size:12px;text-transform:uppercase;
  letter-spacing:.04em;color:var(--t3);font-weight:600;cursor:pointer;
  padding-block:2px;list-style-position:inside}
details.seolinks > summary:hover{color:var(--t1)}
details.seolinks[open] > summary{margin-block-end:6px}
details.seolinks > summary .n{color:var(--t3);margin-inline-start:6px;font-weight:400}
/* Как выглядит объяснение методологии, ушедшее из .seo в подвал под выдачу
   (см. seo.py render_page): те же 12px, но своя обёртка с отступом сверху и
   волосяной линией, а не border-block-start самого details, который здесь
   раньше отделял его от списков ссылок наверху блока. */
.seofoot{max-inline-size:78ch;margin-block-start:24px;padding-inline:20px}
details.howto{font-size:12px;color:var(--t3);border-block-start:1px solid var(--hairline);
  padding-block-start:10px}
details.howto summary{cursor:pointer}
details.howto p{margin:8px 0 0;line-height:1.55;max-inline-size:70ch}
/* Карточка из разметки использует те же классы, что и собранная скриптом,
   но с семантическими тегами: заголовок это заголовок, фрагмент это абзац. */
.card h3.ctitle{margin:0;font-weight:640}
.card p.cfrag{margin:0}

/* --- Правовые страницы (src/legal.py) ------------------------------------
   Подвал общий для всего сайта, но пуст, пока legal.enabled выключен —
   footer_html() тогда возвращает пустую строку и этот блок ни на что не
   влияет. .legalversion — низ самих страниц /terms и т.д., строкой версии
   документа (см. README в docs/legal). */
.legalfooter{max-inline-size:960px;margin:40px auto 0;padding:16px 20px 28px;
  display:flex;flex-wrap:wrap;gap:14px;border-block-start:1px solid var(--hairline)}
.legalfooter a{font-size:12px;color:var(--t3)}
.legalfooter a:hover{color:var(--t1);text-decoration:underline}
.legalversion{font-size:12px;color:var(--t3);margin-block-start:24px}

/* Стили личного кабинета. Файл склеивается ПОСЛЕ styles.css (src/board.py:
   styles_css()) и объявляет собственные переменные — существующие токены
   в styles.css не трогаем ни разу, только добавляем новые имена.

   Новые токены. --ok/--wash-ok — успех/подтверждение (не берём --sale, он
   занят типом сделки «Продажа», хотя оттенок тот же). --info/--wash-info —
   нейтрально-информационный акцент для подсказок; по значению совпадает с
   --accent, отдельное имя нужно, чтобы «эта подсказка» и «это акцент бренда»
   можно было менять по отдельности в будущем. */
:root{
  --ok:#2f5347; --wash-ok:rgba(47,83,71,.10);
  --info:#26375c; --wash-info:rgba(38,55,92,.10);
}
@media(prefers-color-scheme:dark){:root:not([data-theme=light]){
  --ok:#7fb99f; --wash-ok:rgba(127,185,159,.12);
  --info:#a9bde8; --wash-info:rgba(169,189,232,.12);
}}
:root[data-theme=dark]{
  --ok:#7fb99f; --wash-ok:rgba(127,185,159,.12);
  --info:#a9bde8; --wash-info:rgba(169,189,232,.12);
}

/* setAccountChromeVisible() (account.js) прячет каталог атрибутом hidden при
   входе в кабинет. Родной [hidden]{display:none} браузера теряет пары
   очков собственному .wrap{display:grid} (styles.css) при равной
   специфичности — авторский класс побеждает UA-стиль независимо от порядка
   подключения файлов. Без этой строки фильтры и панель каталога оставались
   бы на экране поверх кабинета: проверено вживую в браузере, не только
   чтением кода. */
.wrap[hidden]{display:none}

/* --- Слот входа в шапке ------------------------------------------------------
   min-width держит место в ряду стабильным между «Войти» и будущим именем
   вошедшего человека, чтобы остальные элементы шапки не подпрыгивали. */
.acctwrap{min-inline-size:96px;display:flex;justify-content:flex-end;flex-shrink:0}
.acct{font-size:13px;font-weight:600;color:var(--t2);background:var(--surface);
  border:1px solid var(--border);border-radius:8px;padding:9px 14px;white-space:nowrap;
  transition:border-color var(--dur) var(--ease),color var(--dur) var(--ease)}
.acct:hover{border-color:var(--accent);color:var(--accent-strong)}

/* --- Сердце избранного -------------------------------------------------------
   Базовый вид — вне .popfoot, где своя раскладка на телефоне уже задана в
   styles.css (50×50 внутри @media(max-width:880px)). Здесь стиль для трёх
   мест, где сердце появляется впервые: подвал карточки списка, шапка
   развёрнутой карточки на десктопе, оверлей на карточке избранного. Круглая
   форма и рамка того же радиуса, что у .close/.btn2, а не отдельная система. */
@media(min-width:881px){
  .fav{width:36px;height:36px;border-radius:50%;display:inline-flex;align-items:center;
    justify-content:center;font-size:16px;line-height:1;color:var(--t2);background:var(--surface);
    border:1px solid var(--border);flex-shrink:0;
    transition:color var(--dur) var(--ease),border-color var(--dur) var(--ease),background var(--dur) var(--ease)}
  .fav:hover{color:var(--danger);border-color:var(--danger)}
}
.fav[aria-pressed=true]{color:var(--danger);border-color:var(--danger);background:var(--wash-danger)}
.cfoot .fav{width:32px;height:32px;font-size:14px;margin-inline-start:auto}
.cfoot .btn2 + .fav{margin-inline-start:0}
.pophead .fav{width:32px;height:32px;font-size:14px;margin-inline-start:8px}
.acctcardwrap{position:relative}
.acctcardwrap > .fav{position:absolute;inset-block-start:10px;inset-inline-end:10px;z-index:1;
  background:var(--surface);box-shadow:var(--shadow)}

/* --- Кнопка сохранить поиск, .rbar ------------------------------------------- */
.savesearch{color:var(--accent-strong);border-color:rgba(38,55,92,.35)}
.savesearch:hover{background:var(--wash-info)}

/* --- Тосты -------------------------------------------------------------------
   z-index:30 — выше .popfoot (9) и .menu (20), см. reactive-purring-yeti.md. */
.toasts{position:fixed;inset-block-end:16px;inset-inline-end:16px;z-index:30;
  display:flex;flex-direction:column;gap:8px;max-inline-size:min(360px,calc(100vw - 32px));
  pointer-events:none}
.toast{display:flex;align-items:center;gap:10px;background:var(--surface);color:var(--t1);
  border:1px solid var(--border);border-inline-start:3px solid var(--t3);border-radius:10px;
  padding:11px 14px;font-size:13px;line-height:1.4;box-shadow:var(--shadow-pop);
  animation:toastin .18s var(--ease);pointer-events:auto}
.toast.ok{border-inline-start-color:var(--ok);background:var(--wash-ok)}
.toast .txt{flex:1}
.toast .undo{font-size:12px;font-weight:600;color:var(--accent-strong);white-space:nowrap;
  text-decoration:underline;flex-shrink:0}
@keyframes toastin{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.toast{animation:none}}
@media(max-width:880px){
  .toasts{inset-inline-end:12px;inset-inline-start:12px;inset-block-end:12px;max-inline-size:none}
}

/* --- Диалог подтверждения -----------------------------------------------------
   Переиспользует .scrim как есть (z-index:8): не заводим отдельный
   .cscrim и не поднимаем z-index искусственно, порядок в DOM (после #pop)
   уже даёт диалогу верхний слой среди равных. */
.confirmbox{max-inline-size:420px;margin:15vh auto 0;background:var(--surface);
  border-radius:14px;box-shadow:var(--shadow-pop);border:1px solid var(--border);
  padding:20px;animation:rise .22s var(--ease)}
.confirmbox h2{margin:0 0 8px;font-size:17px;font-weight:640;color:var(--t1)}
.confirmbox p{margin:0 0 16px;font-size:13px;line-height:1.55;color:var(--t2)}
.confirmbox .row2{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap}
@media(prefers-reduced-motion:reduce){.confirmbox{animation:none}}
@media(max-width:480px){.confirmbox{margin-block-start:20vh}}

/* --- Диалог входа --------------------------------------------------------------
   Тот же каркас, что .confirmbox (тень, радиус, отступ сверху), но своя
   ширина под форму и не .row2: кнопки здесь идут одна под другой, а не в ряд. */
.signinbox{max-inline-size:360px;margin:15vh auto 0;background:var(--surface);
  border-radius:14px;box-shadow:var(--shadow-pop);border:1px solid var(--border);
  padding:24px;animation:rise .22s var(--ease)}
.signinbox h2{margin:0 0 16px;font-size:18px;font-weight:660;color:var(--t1)}
.signinbox .field{margin-block-end:12px}
.signinbox button[type=submit]{margin-block-start:4px;width:100%}
.signinor{text-align:center;font-size:12px;color:var(--t3);margin:14px 0;
  display:flex;align-items:center;gap:10px}
.signinor::before,.signinor::after{content:'';flex:1;height:1px;background:var(--hairline)}
.signingoogle,.signintg{width:100%;justify-content:center}
.signintg{margin-block-start:8px}
.signinnote{margin:14px 0 0;font-size:12px;line-height:1.5;color:var(--t3)}
.signinsent{font-size:14px;line-height:1.6;color:var(--t1);margin:0}
@media(prefers-reduced-motion:reduce){.signinbox{animation:none}}
@media(max-width:480px){.signinbox{margin-block-start:20vh}}

/* .primary уже задан в styles.css; .danger — модификатор поверх него на
   фоне --danger, для необратимых действий (удалить сохранённый поиск). */
.primary.danger{background:var(--danger);color:var(--on-accent)}
.primary.danger:hover{background:var(--danger);filter:brightness(.92)}

/* --- Страница кабинета --------------------------------------------------------- */
.acctpage{max-inline-size:640px;margin:0 auto;padding:28px 20px 60px}
.accthead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-block-end:18px}
.backlink{font-size:13px;color:var(--accent-strong);text-decoration:underline}
.cstatus{font-size:12px;font-weight:600;color:var(--t3);background:var(--fill);
  border-radius:20px;padding:4px 10px;white-space:nowrap}
.acctnav{display:flex;gap:6px;flex-wrap:wrap;border-block-end:1px solid var(--border);
  padding-block-end:10px;margin-block-end:20px}
.acctnavbtn{font-size:13px;font-weight:600;color:var(--t2);padding:8px 12px;border-radius:8px;
  min-height:40px;display:inline-flex;align-items:center;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease)}
.acctnavbtn:hover{background:var(--fill);color:var(--t1)}
.acctnavbtn[aria-current=page]{background:var(--accent-fill);color:var(--on-accent)}
.acctbody h1{font-size:21px;font-weight:660;margin:0 0 10px;color:var(--t1)}
.acctlead{font-size:14px;line-height:1.6;color:var(--t2);margin:0}

/* Задел на будущее (профиль, «Мои объявления»): строка «подпись — значение»
   и панель действий карточки. Не используются в этой задаче, объявлены для
   согласованности с формой и будущим статусным экраном. */
.prow{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;padding:8px 0;
  border-block-end:1px solid var(--hairline)}
.prow .plabel{font-size:12px;color:var(--t3);min-inline-size:120px}
.cacts{display:flex;gap:6px;flex-wrap:wrap}
.cact{font-size:13px;font-weight:600;color:var(--t2);padding:8px 10px;border-radius:8px;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease)}
.cact:hover{background:var(--fill);color:var(--t1)}
.cact.danger{color:var(--danger)}
.cact.danger:hover{background:var(--wash-danger)}

/* Избранное и «Мои объявления»: карточки из cardHTML() как есть, сетка своя
   (у каталога своя .grid с плотностью список/сетка, здесь она не нужна —
   кабинет не знает про S.density). */
.acctgrid{display:grid;gap:12px;grid-template-columns:minmax(0,1fr)}
@media(min-width:640px){.acctgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* --- Мои объявления: чипы статусов и причина отказа --------------------------- */
.statuschips{display:flex;gap:6px;flex-wrap:wrap;margin-block-end:16px}
.statuschip{font-size:12px;font-weight:600;color:var(--t2);background:var(--surface);
  border:1px solid var(--border);border-radius:20px;padding:6px 12px;white-space:nowrap;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.statuschip:hover{border-color:var(--accent)}
.statuschip.on{background:var(--accent-fill);color:var(--on-accent);border-color:transparent}
.lreason{font-size:13px;line-height:1.5;color:var(--danger);background:var(--wash-danger);
  border-radius:8px;padding:8px 12px;margin:0 0 8px}

/* --- Контакт автора (попап объявления от пользователя) ------------------------
   .prow/.plabel уже определены выше для профиля — тот же приём «подпись —
   значение» подходит контакту без второй системы строк. */
.contactbox{margin-block-start:8px}
.contactbox .prow:first-child{border-block-start:1px solid var(--hairline)}

/* Сохранённые поиски. */
.acctlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
.acctrow{display:flex;align-items:center;gap:4px;border:1px solid var(--border);
  background:var(--surface);border-radius:10px;padding-inline-end:6px}
.acctrow .cact{flex:1;text-align:start;padding-inline-start:14px;min-inline-size:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Поля формы. Общий набор классов для кабинета (заявка на агентство, карточка
   компании — рендерятся прямо здесь) и для src/ui/form.js (форма подачи,
   отдельный бандл, не загружен на большинстве страниц) — вторую систему
   полей не заводим ни там, ни здесь. form.css эти классы не переопределяет. */
.field{display:flex;flex-direction:column;gap:5px}
.field label{font-size:12px;font-weight:600;color:var(--t2)}
.field input,.field select,.field textarea{background:var(--surface);color:var(--t1);
  border:1px solid var(--border);border-radius:8px;padding:9px 11px;font:inherit;
  font-family:inherit;inline-size:100%;font-size:14px;min-block-size:44px}
.field textarea{min-block-size:120px;line-height:1.55;resize:vertical}
.field select{appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--t3) 50%),
    linear-gradient(135deg,var(--t3) 50%,transparent 50%);
  background-position:calc(100% - 15px) 50%,calc(100% - 10px) 50%;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
  padding-inline-end:32px}
.field input[type=number]{font-variant-numeric:tabular-nums}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;
  border-color:var(--accent);box-shadow:var(--ring)}
.field.err input,.field.err select,.field.err textarea{border-color:var(--danger)}
.field .fielderr{font-size:12px;color:var(--danger)}
/* Звёздочка обязательности видима зрячему и не читается скринридером дважды:
   само поле несёт required, а иконка помечена aria-hidden в разметке. */
.freq{color:var(--danger);margin-inline-start:3px}
.fhint{font-size:11px;line-height:1.45;color:var(--t3);margin:0}
/* Узел ошибки существует всегда, даже пустой: live-регион, вставленный в DOM
   вместе с текстом, скринридер не объявляет — он должен быть на месте заранее.
   Поэтому прячем пустой через :empty, а не через hidden в разметке. */
.fielderr{margin:0;line-height:1.4}
.fielderr:empty{display:none}
.fcheck{display:flex;align-items:flex-start;gap:9px;font-size:13px;line-height:1.5;
  color:var(--t2);padding:6px 0;cursor:pointer}
.fcheck input{inline-size:20px;block-size:20px;flex-shrink:0;margin:1px 0 0;
  accent-color:var(--accent-fill)}
.fchecks{display:flex;flex-direction:column;gap:2px}
.fgrid{display:grid;gap:12px;grid-template-columns:minmax(0,1fr)}
@media(min-width:560px){
  .fgrid.two{grid-template-columns:repeat(2,minmax(0,1fr))}
  .fgrid.three{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* --- «Куда вы дальше» -----------------------------------------------------------
   Нейтральные токены нарочно: --surface/--border/--t1/--t2/--accent(--info),
   не --wash-rent и не другие метки типа сделки — это не объявление, а
   подсказка о поведении. */
.nudge{display:flex;align-items:center;gap:10px;flex-wrap:wrap;border:1px solid var(--border);
  border-inline-start:3px solid var(--info);background:var(--surface);border-radius:10px;
  padding:11px 14px;margin-block-end:14px;font-size:13px}
.nudgetxt{color:var(--t1);flex:1;min-inline-size:180px}
.nudgeacts{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.nudgex{width:28px;height:28px;border-radius:50%;color:var(--t3);flex-shrink:0;
  display:inline-flex;align-items:center;justify-content:center}
.nudgex:hover{background:var(--fill);color:var(--t1)}
