/* Публичный сайт «Венчурной братвы».
 *
 * Тот же язык, что у мини-апы: нулевые скругления, фаска вместо тени,
 * палитра из логотипа, системный шрифт. Токены продублированы, а не
 * подключены из app.css: сайт и приложение живут на разных страницах и
 * разной ширине, и связывать их одним файлом значит однажды сломать оба
 * правкой для одного.
 *
 * Библиотеки под этим больше нет. Раньше сайт стоял на @freedo/ui-lib с
 * классами fd-*, и половина из них тянула чужие скругления и тени — на
 * прямых углах и фаске это выглядело деталью из другого набора.
 */

:root {
  color-scheme: light;

  --ink: #0a1c4a;          /* холст вокруг панелей — контур логотипа */
  --bg: #dfe4ec;           /* панель — хром */
  --bg-2: #eef1f6;         /* второй план внутри панели */
  --line: #0a1c4a;
  --line-soft: #b6bdcb;

  --bevel-hi: #ffffff;
  --bevel-lo: #8b95a8;
  --bevel-out: inset 2px 2px 0 var(--bevel-hi), inset -2px -2px 0 var(--bevel-lo);
  --bevel-in:  inset 2px 2px 0 var(--bevel-lo), inset -2px -2px 0 var(--bevel-hi);

  /* Тёмная подложка под золотое кольцо фокуса. Ложится на 7 пикселей вокруг
     рамки, золото занимает из них с 2-го по 5-й — остаются два тёмных
     ободка, внутренний и внешний, и кольцо видно на любом фоне. */
  --focus-halo: 0 0 0 7px var(--ink);

  --text: #0d1220;
  --text-2: #4a5468;
  --accent: #0b2a86;
  --accent-ink: #ffffff;
  --accent-soft: #cdd8f2;
  --gold: #d8a52a;
  --gold-ink: #1a1204;
  --gold-soft: #f7e6b8;

  /* Смысловые: заливка, буквы, линия. Значения слово в слово те же, что в
     app.css: статус доступности показывают обе поверхности, и «Открыт» на
     личной странице обязан быть того же зелёного, что в приложении. */
  --ok-bg: #cfe9d5;   --ok-text: #0f3d20;   --ok-line: #4e8f63;
  --warn-bg: #f7e6b8; --warn-text: #5a3f00; --warn-line: #b78f1e;
  --bad-bg: #f4d0d0;  --bad-text: #7a1715;  --bad-line: #c06a68;

  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px; --s7: 48px;

  /* Ступенчато: плавное на пиксельной сетке смазывает края. */
  --fast: .12s steps(3, end);

  --шрифт: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
           "Helvetica Neue", Arial, sans-serif;
}

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

img, svg { image-rendering: pixelated; max-width: 100%; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font-family: var(--шрифт);
  font-size: 17px;
  line-height: 1.55;
}

a { color: var(--accent); }

/* ═══════════════════════════════════════════════════════ каркас */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  max-width: 1000px;
  margin: 0 auto;
  padding: var(--s4);
  /* Запасной ход, а не расчёт на него. Ниже шапку поджимают так, чтобы на
     320 всё село в строку, но подпись на кнопке ещё поменяют, а системный
     шрифт на андроиде шире, чем здесь. Если места не хватит, вниз уедет
     кнопка, а не название: разорванное надвое «Венчурная братва» читается
     как опечатка, кнопка на своей строке — как решение. */
  flex-wrap: wrap;
}
/* Съезжая на свою строку, кнопка держится правого края: слева она выглядит
   вторым названием. */
.topbar .btn { margin-left: auto; }

.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: #fff;
  font-weight: 800;
  font-size: 19px;
  letter-spacing: -.01em;
  text-decoration: none;
  /* Название не переносится. На 320 оно ломалось на «Венчурная» и «братва»,
     шапка вырастала с 66 до 91 пикселя и съедала первый экран. */
  white-space: nowrap;
}
.logo img { width: 34px; height: 34px; flex: none; }
/* Название — ссылка «на главную», и единственная кликабельная вещь на сайте,
   которая под мышкой не отзывалась ничем: ни кнопки, ни карточки, ни строки
   контакта нет без отклика на наведение и нажатие. Подчёркивание, а не цвет:
   перекрашивать имя сообщества под курсором — это уже про другое. */
.logo:hover { text-decoration: underline; text-underline-offset: 3px; }
.logo:active { color: var(--gold); }
/* Своего кольца у названия не было, и с клавиатуры оно получало браузерное:
   синяя нитка в один пиксель, #005fcc поверх полотна #0a1c4a — 2.75:1. То
   есть первая же остановка табом была невидимой. Подложка здесь не нужна:
   полотно и так тёмное, золота хватает. */
.logo:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }

main {
  max-width: 1000px;
  margin: 0 auto;
  padding: 0 var(--s4) var(--s7);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

section {
  background: var(--bg);
  border: 1px solid var(--line);
  box-shadow: var(--bevel-out);
  padding: var(--s5);
}

footer {
  max-width: 1000px;
  margin: 0 auto;
  padding: 0 var(--s4) var(--s6);
  color: #9fb0d6;
  font-size: 14px;
}
footer p { margin: 0 0 var(--s1); }
/* Вторая строка подвала. Класс .muted сюда не годится: он красит текст в
   --text-2, цвет для светлых панелей, а подвал лежит на тёмном полотне —
   получалось 1.7:1, то есть текст, который просто не виден. */
footer .footnote { color: #7488b5; }

/* ═══════════════════════════════════════════════════════ типографика */

h1 {
  margin: 0 0 var(--s3);
  font-size: clamp(28px, 5vw, 42px);
  line-height: 1.1;
  letter-spacing: -.02em;
  font-weight: 800;
  /* На странице встречи в заголовок попадает название, которое написал
     человек в боте. Одно длинное слово крупным кеглем шире экрана телефона,
     и без переноса оно уводит вбок всю страницу. */
  overflow-wrap: anywhere;
}
h2 { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: -.01em; }
p { margin: 0 0 var(--s3); }
p:last-child { margin-bottom: 0; }

/* Подзаголовок и приглушённый текст на странице встречи — тоже чужие строки:
   место, ведущий, «как пройти». Ссылку на карту туда кладут регулярно. */
.lead { font-size: 19px; color: var(--text); overflow-wrap: anywhere; }
.muted { color: var(--text-2); overflow-wrap: anywhere; }
.small { font-size: 14px; }

/* Описание встречи — на странице встречи и на плитке афиши. Его набирает
   организатор в боте, и переводы строк он ставит осмысленно: у встреч с
   программой лекции описание идёт блоками, а разбор формата — по пунктам.
   Без pre-line всё это слипалось в один абзац ровно на той странице, куда
   люди приходят по ссылке из анонса. В мини-аппе и в боте абзацы видны, и
   расходился только веб.

   Класс отдельный, а не правило на .muted: тем же .muted набраны служебные
   строки витрины («У каждого открытая страница», «Что уже прошло»), которые
   написаны в коде и переводов строк не содержат.

   Не путать с .event .text ниже: тот задаёт кегль плитки, этот — переносы, и
   на афише они висят на одном абзаце вместе. */
.event-text { white-space: pre-line; }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s3);
}

/* Надстрочная метка: «бета», «открытая встреча». */
.beta {
  display: inline-block;
  margin: 0 0 var(--s3);
  padding: var(--s1) var(--s2);
  background: var(--gold-soft);
  border: 1px solid var(--gold);
  color: var(--gold-ink);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
}

/* ═══════════════════════════════════════════════════════ кнопки */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 44px;
  padding: 0 var(--s4);
  border: 1px solid var(--line);
  box-shadow: var(--bevel-out);
  background: var(--bg-2);
  color: var(--text);
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--fast);
}
.btn:hover { background: #fff; }
.btn:active { box-shadow: var(--bevel-in); }
/* Кольцо двухцветное, и это не украшение. Одно золото стояло здесь раньше, и
   на панели --bg оно даёт 1.8:1 — полоса, которую видно только если знать,
   что она есть. Тёмный ободок кладётся под золото и вокруг него: на светлой
   панели работает ободок (12.9:1), на тёмном полотне шапки — золото (7.3:1).
   Порядок важен: `.btn:active` стоит выше и той же силы, поэтому нажатую и
   одновременно сфокусированную кнопку добирает отдельное правило ниже. */
.btn:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 2px;
  box-shadow: var(--bevel-out), var(--focus-halo);
}
.btn:focus-visible:active { box-shadow: var(--bevel-in), var(--focus-halo); }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.btn-primary:hover { background: #0d3099; }

.btn-lg { min-height: 52px; padding: 0 var(--s5); font-size: 18px; }
.btn-sm { min-height: 36px; padding: 0 var(--s3); font-size: 14px; }

/* Пальцем в 36 пикселей попадают через раз, а мелкие кнопки здесь несут
   главные действия: «Открыть в Telegram» в шапке и «Подробнее» на карточке
   встречи. На мыши 36 экономят место, на касании этой экономии не бывает. */
@media (pointer: coarse) {
  .btn-sm { min-height: 44px; padding: 0 var(--s4); }
  /* Название в шапке — это ссылка «на главную», и мимо неё пальцем попадали:
     строка высотой 34 пикселя при знаке в 34 пикселя. Ширины хватало и так,
     не хватало высоты. */
  .logo { min-height: 44px; }
  /* Название встречи на афише: строка в 22 пикселя. Длинное название
     переносилось на две строки и случайно набирало 45 — короткое, вроде
     «Переклички», так и оставалось вдвое ниже пальца. Поля добирают высоту,
     отрицательные отступы возвращают строку на место: мишень выросла, вёрстка
     не сдвинулась. Кнопка «Подробнее» рядом ведёт туда же, но клик по
     названию — первое, что человек пробует. */
  .event h3 a { padding-block: 12px; margin-block: -12px; }
}

/* ═══════════════════════════════════════════════════════ чипы и числа */

.chips { display: flex; flex-wrap: wrap; gap: var(--s2); margin: 0; }
/* Призыв под плашками стоял к ним вплотную и читался как ещё одна плашка,
   только другой ширины. Отступ возвращает ему статус отдельного действия. */
.numbers + .chips { margin-top: var(--s4); }

.chip {
  display: inline-flex;
  align-items: center;
  /* Промежуток между значком и словом — как у чипа в приложении. На витрине
     чип носит одно слово и промежутку нечего разделять, а на личной странице
     в нём кружок статуса и медальная эмодзи. */
  gap: var(--s1);
  min-height: 32px;
  padding: 0 var(--s3);
  background: var(--bg-2);
  border: 1px solid var(--line-soft);
  box-shadow: var(--bevel-out);
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  /* Темы каталога и имена ведущих пишут люди, и слово вроде
     «Интернационализациявнешнеэкономическойдеятельности» приезжает целиком.
     Плашка меряется по самому длинному слову, а перенести его нечем: на 320
     такая плашка уезжала на 170 пикселей вправо и тащила за собой прокрутку
     всей страницы. Предел ширины держит её в строке, перенос — внутри. */
  max-width: 100%;
  overflow-wrap: anywhere;
}
.chip-gold { background: var(--gold-soft); border-color: var(--gold); color: var(--gold-ink); }

.numbers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s2);
  margin-top: var(--s4);
}
.numbers.few { grid-template-columns: repeat(auto-fit, minmax(160px, 210px)); }

.number {
  background: var(--bg-2);
  border: 1px solid var(--line-soft);
  box-shadow: var(--bevel-out);
  padding: var(--s3);
  text-align: center;
}
/* Подпись под числом длиной в слово («человекпредпринимателей») распирала
   плашку изнутри: на 320 две плашки в ряду вылезали за правый край панели. */
.number strong { display: block; font-size: 26px; line-height: 1.15; font-weight: 800; overflow-wrap: anywhere; }
.number small { display: block; color: var(--text-2); font-size: 13px; margin-top: 2px; overflow-wrap: anywhere; }

/* ═══════════════════════════════════════════════════════ лица и встречи */

.faces { display: flex; flex-wrap: wrap; gap: var(--s1); margin-top: var(--s4); }

.face.more { background: var(--accent-soft); color: var(--accent); }

/* Фотография внутри кружка. Правила не было вовсе: картинка держалась только
   на общем `img { max-width: 100% }`, то есть подгонялась по ширине, а высота
   оставалась своей. Квадратная аватарка попадала в плитку случайно, любая
   другая вылезала за неё и ломала ряд. В мини-апе это правило есть с самого
   начала, на витрину его просто не перенесли. */
.face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.face {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  box-shadow: var(--bevel-out);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 800;
  font-size: 14px;
  /* Обрезает фотографию по квадрату плитки. Раньше стояло вторым правилом
     `.face` отдельной строкой выше — то же имя, то же место, а искать
     свойство приходилось в двух местах. */
  overflow: hidden;
}

/* Портреты. Блок собирает _portraits_block в web.py и отдаёт разметку
   .portraits > .portrait-grid > .portrait > (img, .who > (b, small)).
   Правил на него не было ни одного, и это видно только на живой базе: в
   тестах блок не появляется, потому что его показывают лишь тем, у кого есть
   настоящая фотография, а в засеве фотографий нет. На боевой витрине из-за
   этого карточки рисовались строчными ссылками высотой в двадцать пикселей,
   аватарки растягивались во всю ширину панели, а два человека наезжали друг
   на друга в одной строке. */

.portrait-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--s2);
  margin-top: var(--s4);
}

.portrait {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3);
  background: var(--bg-2);
  border: 1px solid var(--line-soft);
  box-shadow: var(--bevel-out);
  color: var(--text);
  text-decoration: none;
  /* Ячейка сетки по умолчанию не уже своего содержимого, а содержимое тут —
     чужая подпись в одну строку. Без этого длинная подпись раздвигала бы
     колонку вместо того, чтобы обрезаться. */
  min-width: 0;
}
.portrait img {
  width: 52px;
  height: 52px;
  flex: none;
  object-fit: cover;
  display: block;
  border: 1px solid var(--line);
}
.who { display: flex; flex-direction: column; min-width: 0; }
.who b { font-weight: 800; font-size: 16px; line-height: 1.25; overflow-wrap: anywhere; }
/* Подпись человек пишет сам, и пишет сколько хочет: в базе лежат строки на
   полтораста знаков. Две строки — столько же, сколько занимает имя с полем,
   и карточки в ряду остаются одной высоты. */
.who small {
  color: var(--text-2);
  font-size: 13px;
  line-height: 1.3;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

a.portrait:hover { background: #fff; }
a.portrait:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 2px;
  box-shadow: var(--bevel-out), var(--focus-halo);
}
a.portrait:active { box-shadow: var(--bevel-in); }
a.portrait:focus-visible:active { box-shadow: var(--bevel-in), var(--focus-halo); }

/* Встречи. Классы взяты из разметки, которую собирает _event_card в web.py:
   .event-list > .event > (h3, .when, .text, .row). Раньше здесь стояли
   выдуманные .cards/.card, и список рисовался браузерным по умолчанию — с
   точками и без рамок. */

/* `min(300px, 100%)` вместо голых 300px. Голые 300 — это не «не уже трёхсот»,
   а «ровно триста, даже если места 254»: на 320 колонка вылезала за панель, и
   вся страница ехала вбок на 13 пикселей. Ниже трёхсот колонка теперь просто
   сжимается до ширины родителя, а выше ведёт себя как раньше. */
.event-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: var(--s3);
  margin: 0;
  padding: 0;
  list-style: none;   /* точки маркеров: список тут плитками, а не перечнем */
}

.event {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s4);
  background: var(--bg-2);
  border: 1px solid var(--line-soft);
  box-shadow: var(--bevel-out);
}
/* Название и описание встречи набирает человек в боте. Туда попадают ссылки
   на регистрацию и слова без пробелов, длиннее плитки: без переноса они
   распирали плитку и выталкивали афишу за экран. */
.event h3 { margin: 0; font-size: 18px; font-weight: 800; line-height: 1.25; overflow-wrap: anywhere; }

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

   display: block, а не строчный элемент. Строчная ссылка меряется по буквам:
   «Перекличка» это мишень 112 на 21, и попасть в неё пальцем можно только с
   первого раза. Блок занимает всю строку заголовка, кольцо фокуса выходит
   одним прямоугольником, а не обрывком на каждой строке переноса. */
.event h3 a {
  display: block;
  color: var(--accent);
  transition: background var(--fast);
}
.event h3 a:hover { background: #fff; }
.event h3 a:active { box-shadow: var(--bevel-in); }
.event h3 a:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 2px;
  box-shadow: var(--focus-halo);
}
/* Как и у кнопки: `:active` стоит выше и той же силы, поэтому нажатую и
   одновременно сфокусированную ссылку добирает отдельное правило. */
.event h3 a:focus-visible:active { box-shadow: var(--bevel-in), var(--focus-halo); }

.event .when { margin: 0; color: var(--text-2); font-size: 14px; overflow-wrap: anywhere; }
.event .text { margin: 0; font-size: 15px; overflow-wrap: anywhere; }

/* Чипы и кнопка в подвале плитки. margin-top:auto прижимает их вниз, чтобы у
   соседних плиток разной высоты кнопки стояли на одной линии. */
.event .row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin-top: auto;
  padding-top: var(--s1);
}

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

   Число подобрано по контрасту, а не на глаз. Прозрачность гасит не плитку, а
   каждую букву в ней, и множится на то, что уже посчитано: строка даты набрана
   --text-2 по --bg-2, это 6.7:1 с запасом, но при .78 от неё остаётся 3.94:1 —
   ниже порога. Дата и место — единственное, ради чего прошедшую встречу вообще
   читают. При .85 выходит 4.63:1, и приглушённость никуда не девается. */
.events.past .event { opacity: .85; }
.events.past .event:hover { opacity: 1; }

/* Строка вместо пустой рамки, когда афиша пуста. */
.empty { margin: 0; color: var(--text-2); }

/* ═══════════════════════════════════════════════════════ личная страница */

/* Страница человека, /u/{slug}. Она одна из четырёх поверхностей стояла на
 * @freedo/ui-lib со слоем landing.css поверх: круглые пилюли, фиолетовый
 * акцент, мягкие тени, своя тёмная тема и никакой шапки. С витрины сюда ведут
 * портреты из блока «Кто здесь есть», и человек, кликнув по лицу, попадал в
 * другой продукт, из которого некуда вернуться.
 *
 * Ниже только то, чего на витрине ещё не было: чужой текст из анкеты, чип
 * доступности, строка «сейчас ищу», рекомендации и контакты. Всё остальное на
 * этой странице собрано из тех же section, .section-head, .chip и .btn.
 */

/* Текст, который человек написал о себе сам. Переводы строк он ставил
 * осмысленно: без pre-line абзацы слипались в один. Класс назван так же, как
 * в приложении, — это ровно тот же кусок текста, показанный в другом месте. */
.about-text {
  margin: 0;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* Доступность. Чип из тех же деталей, что остальные, плюс цвет. Цвет несёт
 * смысл, но не в одиночку: рядом всегда слово, и на бумаге, где заливка не
 * печатается, остаётся оно. Заголовки статусов короткие и заданы в коде,
 * поэтому строку не переносим: «В завале» в две строки читается как сбой. */
.status { white-space: nowrap; }
.status .dot { width: 14px; height: 14px; flex: none; }

/* Порядок важен: правила стоят после .chip и той же силы, иначе заливку чипа
   не перебить. Свойства лежат в своих правилах, а не в общем селекторе. */
.status-open     { background: var(--ok-bg);   color: var(--ok-text);   border-color: var(--ok-line); }
.status-business { background: var(--warn-bg); color: var(--warn-text); border-color: var(--warn-line); }
.status-busy     { background: var(--bad-bg);  color: var(--bad-text);  border-color: var(--bad-line); }

/* «Сейчас ищу» — строка, которую человек меняет чаще всего остального на
 * странице, и единственная с сегодняшним сроком годности. Отбита слева
 * акцентом: в приложении она отбита им же. */
.seeking {
  margin: var(--s4) 0 0;
  padding: var(--s3);
  border: 1px solid var(--line-soft);
  border-left: 4px solid var(--accent);
  background: var(--bg-2);
  box-shadow: var(--bevel-out);
  overflow-wrap: anywhere;
}

/* Рекомендации. Чужие слова весомее собственного описания, поэтому им плитка
 * и кавычка, а не строка перечня. */
.recs { display: grid; gap: var(--s3); }

.rec {
  margin: 0;
  padding: var(--s4);
  border: 1px solid var(--line-soft);
  border-left: 4px solid var(--accent);
  background: var(--bg-2);
  box-shadow: var(--bevel-out);
}
.rec blockquote { margin: 0; overflow-wrap: anywhere; }

/* Подпись под цитатой: имя, чем занимается, откуда знает. Три куска в строку,
   на узком переносятся по одному. */
.rec figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s1) var(--s3);
  margin-top: var(--s2);
  font-size: 14px;
}
.rec figcaption b { font-weight: 800; overflow-wrap: anywhere; }
.rec figcaption span { color: var(--text-2); overflow-wrap: anywhere; }
.rec figcaption em { font-style: normal; color: var(--text-2); }

/* Контакты. Строка кликается целиком: подпись слева и значение справа по
 * отдельности — две мишени вместо одной, и обе уже пальца. */
.links { display: grid; gap: var(--s2); }

.links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  min-height: 44px;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line-soft);
  box-shadow: var(--bevel-out);
  background: var(--bg-2);
  color: var(--text);
  text-decoration: none;
  /* Адрес человек вписывает сам, и в поле приезжает строка без пробелов
     длиннее экрана: на 320 она уводила вбок всю страницу. */
  overflow-wrap: anywhere;
}
.links a:hover { background: #fff; }
.links a:active { box-shadow: var(--bevel-in); }
.links a:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 2px;
  box-shadow: var(--bevel-out), var(--focus-halo);
}
.links a:focus-visible:active { box-shadow: var(--bevel-in), var(--focus-halo); }

/* Подпись слева: «Телефон», «Почта», «Сайт». Не сжимается, иначе длинное
   значение справа выдавливает её в столбик из букв. */
.links .k { flex: none; color: var(--text-2); font-size: 14px; font-weight: 700; }

/* ═══════════════════════════════════════════════════════ карта встречи */

/* Рамка держит пропорцию сама, без скрипта: отступ снизу в процентах считается
   от ширины родителя, и карта не прыгает при загрузке. */
.map {
  position: relative;
  height: 0;
  padding-bottom: 56%;
  margin: var(--s3) 0;
  border: 1px solid var(--line);
  box-shadow: var(--bevel-out);
  background: var(--bg-2);
  overflow: hidden;
}
.map iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  /* Карта единственная картинка на сайте, которой пиксельность во вред:
     подписи улиц от неё рассыпаются. */
  image-rendering: auto;
}

/* На телефоне карта выше по пропорции: узкая и плоская показывает две улицы
   и ничего вокруг. */
@media (max-width: 640px) {
  .map { padding-bottom: 75%; }
}

/* Печатать карту бессмысленно: серый прямоугольник на полстраницы. Адрес и
   ссылки рядом остаются. */
@media print {
  .map { display: none; }
}

.where .lead { margin-bottom: 0; }
.chips.calendar { margin-top: var(--s3); }

/* ═══════════════════════════════════════════════════════ как попасть */

.join ol { margin: 0 0 var(--s4); padding-left: 1.3em; }
.join li { margin-bottom: var(--s2); }

.wide-only { display: none; }
@media (min-width: 560px) { .wide-only { display: inline; } }

@media (max-width: 640px) {
  body { font-size: 16px; }
  section { padding: var(--s4); }
  .lead { font-size: 17px; }
  /* По одному числу в строке четыре плашки съедали весь экран, и до афиши
     приходилось листать. Два в ряд читаются так же и занимают вдвое меньше. */
  .numbers, .numbers.few { grid-template-columns: 1fr 1fr; }
  .number strong { font-size: 22px; }
  /* На странице встречи плашек бывает одна: у встречи есть ведущий, но никто
     ещё не отметился. Одна плашка в сетке из двух колонок занимала левую
     половину и оставляла пустую правую — читалось как «вторая не загрузилась».
     А имя ведущего в 22 пикселя жирным, зажатое в 120 точек, ломалось
     посреди слов: «коллеги/и адвокат/ов Санкт-Петербу/рга». */
  .numbers .number:only-child { grid-column: 1 / -1; }
  /* Плитки с инициалами — украшение, а не содержание: на узком они не
     должны отжимать вниз всё остальное. */
  .face { width: 36px; height: 36px; font-size: 12px; }
  /* Главное действие на телефоне во всю ширину: мимо не промахнёшься, и
     видно, что оно тут одно, а не первое из нескольких.

     Не только .btn-lg. На странице ошибки главные двери — «На главную» и
     «Открыть бота» — набраны обычным .btn, и правило их не находило: две
     кнопки разной ширины жались к левому краю, пока на остальных трёх
     страницах то же самое действие шло полосой во всю панель. Мелкие .btn-sm
     исключены нарочно: «Яндекс Карты» и «Google Карты» стоят парой в строке,
     и растягивать их незачем. */
  .chips .btn:not(.btn-sm) { width: 100%; }
  h1 { font-size: clamp(24px, 7vw, 32px); }
}

/* Самые узкие телефоны, 320 и 360. Здесь шапка не сходится: название в
   полном размере плюс кнопка «Telegram» шире, чем 288 пикселей между
   полями, и название ломалось надвое. Поджимаем поля, промежуток и сам
   знак — в строку садится всё, включая кнопку в 44 пикселя для пальца.
   Порог 365, а не 400: на 375 и 414 полный размер помещается сам. */
@media (max-width: 365px) {
  .topbar { padding: var(--s3); gap: var(--s2); }
  .logo { font-size: 16px; gap: var(--s1); }
  .logo img { width: 30px; height: 30px; }
  main { padding: 0 var(--s3) var(--s6); }
  footer { padding: 0 var(--s3) var(--s5); }
}

/* Сайт открывают и с тёмной темой в системе. Полотно у нас и так тёмное,
 * а панели остаются светлыми намеренно: это хром из логотипа, и в тёмном
 * исполнении он перестаёт быть хромом. Меняем только то, что читается
 * поверх холста. */
@media (prefers-color-scheme: dark) {
  footer { color: #93a6cf; }
}

/* ═══════════════════════════════════════════════ гранты и программы */

/* Календарь на /grants и страница программы на /g/{slug}. Правила свои, а не
   дописанные к встречам через запятую: у плитки программы другие части —
   состояние окна подачи, пометка непроверенного, вид записи над названием, —
   и групповой селектор в этом файле уже дважды перебивал правило компонента.
   Токены при этом те же, и обе плитки читаются как один продукт. */

.grant-list {
  display: grid;
  /* `min(320px, 100%)` вместо голых 320: голые триста двадцать на экране в
     320 пикселей вылезают за панель вместе с полями и уводят страницу вбок. */
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--s3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.grant {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s4);
  background: var(--bg-2);
  border: 1px solid var(--line-soft);
  box-shadow: var(--bevel-out);
}

/* Строка над названием: вид записи и организатор. Она отвечает на вопрос
   «чей это набор», который у гранта стоит раньше всех прочих: по «Студенческому
   стартапу» без слова ФСИ нельзя понять вообще ничего. */
.grant .kicker {
  margin: 0;
  color: var(--text-2);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

/* Название набирает человек в админке, и в него попадают длинные слова без
   пробелов: без переноса они распирают плитку и выталкивают список за экран. */
.grant h3 { margin: 0; font-size: 18px; font-weight: 800; line-height: 1.25; overflow-wrap: anywhere; }

/* Название ведёт на страницу программы, и отзывается оно так же, как всё
   кликабельное на сайте: наведение, нажатие и золотое кольцо с тёмной
   подложкой. Блок во всю строку заголовка: строчная ссылка меряется по
   буквам, и в короткое название пальцем попадают через раз. */
.grant h3 a {
  display: block;
  color: var(--accent);
  transition: background var(--fast);
}
.grant h3 a:hover { background: #fff; }
.grant h3 a:active { box-shadow: var(--bevel-in); }
.grant h3 a:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 2px;
  box-shadow: var(--focus-halo);
}
/* `:active` стоит выше и той же силы, поэтому нажатую и сфокусированную
   одновременно ссылку добирает отдельное правило. */
.grant h3 a:focus-visible:active { box-shadow: var(--bevel-in), var(--focus-halo); }

.grant .text { margin: 0; font-size: 15px; overflow-wrap: anywhere; }

/* Чипы и кнопки в подвале плитки. `margin-top: auto` на последнем ряду
   прижимает кнопки вниз, чтобы у соседних плиток разной высоты они стояли на
   одной линии. */
.grant .row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  padding-top: var(--s1);
}
.grant .row.actions { margin-top: auto; }

/* ─────────────────────────────────────────── состояние окна подачи */

/* Подпись срока, окрашенная по состоянию. Цвет тут несёт смысл: в списке из
   пятнадцати записей живой приём отличают от закрытого раньше, чем дочитывают
   строку. Цветом он при этом не ограничивается — слово написано целиком, и
   человеку, который цвета не различает, видно ровно то же самое. */
.window {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: 30px;
  margin: 0;
  padding: 0 var(--s3);
  border: 1px solid var(--line-soft);
  font-size: 14px;
  font-weight: 700;
  max-width: 100%;
  overflow-wrap: anywhere;
}
/* В плитке списка расстояние держит промежуток самой плитки. На первом
   экране страницы программы промежутка нет, и описание вставало вплотную к
   рамке срока: две разные вещи читались как одна. */
.hero .window { margin-bottom: var(--s3); }

.window-open { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok-text); }
/* Горящий срок: неделя до дедлайна. Тот же жёлтый, что у предупреждений в
   приложении, и он единственный на плитке. */
.window-hot { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn-text); }
.window-soon { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.window-closed { background: var(--bg); border-color: var(--line-soft); color: var(--text-2); }
.window-none { background: var(--bg); border-color: var(--line-soft); color: var(--text-2); }

/* ─────────────────────────────────────────── пометка непроверенного */

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

   Цвета предупреждения, потому что цена ошибки здесь высокая: по чужому
   дедлайну человек планирует неделю работы. */
.approx {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 var(--s2);
  background: var(--warn-bg);
  border: 1px dashed var(--warn-line);
  color: var(--warn-text);
  font-size: 13px;
  font-weight: 700;
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* Сумма и пометка к ней одним куском. Врозь они разъезжались переносом, и
   «сумма примерная» вставала вплотную к первой теме: пометка оказывалась
   прицеплена к чужому слову. */
.money {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2);
  max-width: 100%;
}

/* ─────────────────────────────────────────── что известно о программе */

/* Пары «поле — значение» на странице программы. Список определений, а не
   таблица: на телефоне таблица из двух колонок ломает длинную строку суммы
   посреди слова. */
.facts { margin: 0; display: grid; gap: var(--s2); }
.fact {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s1) var(--s3);
  padding: var(--s2) 0;
  border-top: 1px solid var(--line-soft);
}
.fact:first-child { border-top: 0; }
.fact dt { min-width: 150px; color: var(--text-2); font-size: 14px; font-weight: 700; }
.fact dd { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); overflow-wrap: anywhere; }

/* ─────────────────────────────────────────── отбор */

/* Подписи рядов отбора стоят вплотную к своим кнопкам: с обычным отступом
   абзаца слово «Тема» висело ровно посередине между двумя рядами и читалось
   как подпись к предыдущему. */
.filters p.small { margin-bottom: var(--s1); }
.filters .chips { margin-bottom: var(--s4); }
.filters .chips:last-child { margin-bottom: 0; }

/* Включённая кнопка отбора. Золото, как у надстрочной метки: акцентная синяя
   заливка здесь спорила бы с главным действием страницы, кнопкой подачи. */
.btn-on {
  background: var(--gold-soft);
  border-color: var(--gold);
  color: var(--gold-ink);
}
.btn-on:hover { background: #fff1cf; }

@media (max-width: 640px) {
  /* Подпись поля над значением: на 320 колонка в 150 пикселей оставляла
     сумме половину экрана, и «до 4 млн ₽ на первый год» ломалось на четыре
     строки. */
  .fact dt { min-width: 100%; }
}

@media print {
  /* Плитку не режем между листами: половина программы на одной странице,
     вторая на другой — это уже не программа. */
  .grant { break-inside: avoid; }
  /* Пометка непроверенного обязана дожить до бумаги: распечатанный список
     сроков без неё выглядит сверенным. */
  .approx { border-style: dashed; color: var(--ink); }
  /* Заливки браузер не печатает, и состояние осталось бы голой строкой без
     границы: рамка отделяет подпись срока от текста вокруг. */
  .window { border-color: var(--ink); color: var(--ink); }
}

/* ═══════════════════════════════════════════════════════ бумага */

/* Правил для печати не было вовсе, а страницу встречи распечатывают: её
 * вешают в баре и приносят на саму встречу. Браузер по умолчанию не печатает
 * фоны, и на бумагу уходило вот что: тёмное полотно исчезает, а поверх белого
 * остаётся белое название в шапке, белые подписи на кнопках и подвал цветом
 * #9fb0d6, который на белом даёт 2.2:1. То есть ровно те строки, ради которых
 * лист и печатали, оказывались пустыми местами.
 *
 * Свойства разложены по правилам самих компонентов, а не собраны в один
 * групповой селектор: в этом файле уже дважды такой селектор перебивал
 * правило компонента, и оба раза это находили не сразу.
 */
@media print {
  body { background: #fff; }

  /* Название рисовалось белым по тёмному полотну. Полотна на бумаге нет. */
  .logo { color: var(--ink); }

  /* Подвал набран под тёмный фон: на белом это 2.2:1 и 3.0:1. */
  footer { color: var(--text); }
  footer .footnote { color: var(--text-2); }

  /* Синяя заливка кнопки не печатается, а белые буквы на ней — печатаются
     белым. Оставляем рамку и тёмный текст. */
  .btn {
    background: none;
    color: var(--ink);
    border-color: var(--ink);
    max-width: 100%;
    overflow-wrap: anywhere;
  }
  .btn-primary { background: none; color: var(--ink); border-color: var(--ink); }
  /* На бумаге по кнопке не щёлкнешь: дописываем адрес, ради которого она тут
     и стоит. */
  .btn[href^="http"]::after { content: " " attr(href); font-weight: 400; }

  /* Кроме тех адресов, которые собрала машина. Ссылку в Google Календарь
     склеивают из названия, описания и адреса встречи, всё в процентах: на
     листе это полторы тысячи знаков вида %D0%9F%D1%80 — четверть страницы
     мусора там, где человек ждёт афишу. Ссылки на карты того же рода. Ни ту,
     ни другую с бумаги не набирают: на встречу идут по адресу, который
     напечатан строкой выше. Подписи кнопок остаются, уходят только адреса. */
  .chips.calendar .btn::after { content: none; }
  .where .chips .btn::after { content: none; }

  /* Мозаика инициалов — украшение, и цвет плитки задан прямо в разметке.
     Заливка не печатается, буквы на ней белые: вышел бы ряд пустых квадратов. */
  .faces { display: none; }

  /* Плитку не режем между листами: половина встречи на одной странице, вторая
     на другой — это уже не афиша. */
  .event { break-inside: avoid; }
  .portrait { break-inside: avoid; }
  .number { break-inside: avoid; }
  /* Цитату не режем: половина рекомендации на одном листе, вторая на другом —
     это уже не рекомендация. Разделу целиком «не резать» тут мало: когда
     рекомендаций много, раздел выше листа, и запрет браузер игнорирует. */
  .rec { break-inside: avoid; }
  .links a { break-inside: avoid; }
  section { break-inside: avoid; }
}
