/*****************************
       Card (общий вид панелей)
******************************/
.week-card,
.month-layout,
.modal-box,
.auth-box,
.event-detail-card,
.error-page-card {
  background-color: var(--bg-surface);
  border: 1px solid rgba(var(--overlay-rgb), 0.08);
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  color: var(--text-primary);
}

/*****************************
       Week page layout
******************************/
.week-card {
  position: relative;
  padding: 15px;
  max-width: 1200px;
  margin: 0 auto;
  overflow-x: auto;
}

/* Быстрый доступ к "Мои категории"/"Мои шаблоны" прямо с сетки недели/месяца
   (см. CategoryModal.js/TemplateModal.js) - те же .category-btn-trigger/
   .template-btn-trigger классы, что и в _nav.html/profile.html, поэтому
   Planner.js/MonthView.js подключают эти кнопки без каких-либо изменений в
   JS. Абсолютное позиционирование в углу карточки, а не внутри .week-header/
   .month-header - те центрируют prev/title/next, и добавление туда ещё
   одного flex-элемента сломало бы центрирование. */
.calendar-header-actions {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 2;
}

/* "Организовать событие" - реальная видимая кнопка (не ещё один пункт
   .nav-menu-dropdown выше) рядом с шестерёнкой, см. её же комментарий в
   _calendar_header_actions.html. white-space: nowrap - "Организовать
   событие" не должно переноситься на вторую строку внутри узкой .nav-btn. */
.calendar-organize-btn {
  padding: 6px 10px;
  white-space: nowrap;
  text-decoration: none;
}

.week-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  margin-bottom: 15px;
}

.week-title {
  font-size: 1.2em;
  font-weight: bold;
  min-width: 140px;
  text-align: center;
  color: var(--text-primary);
}

.nav-btn {
  padding: 8px 12px;
  border: 1px solid rgba(var(--overlay-rgb), 0.1);
  border-radius: 10px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
  font-weight: bold;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

/* Тот же вид "выключено", что и у .event-save:disabled/.details-delete:disabled
   (см. CLAUDE.md "Comments"/"Group settings") - .nav-btn ни разу не бывал
   disabled раньше самой кнопки "Оповестить группу" (см. event_detail.html/
   GROUP_NOTICE_COOLDOWN_HOURS в views.py), отсюда только теперь. */
.nav-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  transform: none;
}

.nav-btn:hover {
  background-color: var(--bg-header);
  transform: translateY(-2px);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.35);
}

.nav-btn:active {
  transform: translateY(0);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

/*****************************
       Page navigation
******************************/
/* Раньше - просто ряд текстовых плашек-ссылок; теперь тот же язык иконок,
   что уже был только в мобильном .bottom-nav (см. комментарий в
   schedule/_nav.html про то, откуда взяты те же самые svg).

   По запросу ("отцентруем блок кнопок... слева заглушку под логотип...")
   .page-nav переведён с flex (лого-плашка + ссылки + spacer-затычка + профиль,
   всё прижато по краям) на grid с тремя колонками 1fr auto 1fr - ОБЕ крайние
   колонки одинаково гибкие (1fr) и забирают всё оставшееся место ПОРОВНУ
   независимо от того, что в них лежит (лого слева, .page-nav-right справа),
   поэтому средняя auto-колонка (.page-nav-links) всегда оказывается ровно
   по центру всей панели, а не только по центру места, оставшегося после уже
   сверстанных по краям блоков - будь колонки 1fr auto auto (обе крайние
   разной "гибкости"), центрирования бы не получилось, вся лишняя ширина
   уходила бы только в первую 1fr-колонку. Старый .nav-spacer (flex-grow:1,
   чисто затычка между левым и правым блоком) с этим больше не нужен и убран
   целиком, вместе с разметкой. */
.page-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 10px;
  padding: 8px;
  margin-bottom: 15px;
  background-color: var(--bg-surface-2);
  border: 1px solid rgba(var(--overlay-rgb), 0.06);
  border-radius: 18px;
}

/* Заглушка под логотип - явный ask, "слева от кнопок... размером +- с
   профиль справа". min-width - не просто размер самого кружка (см.
   .page-nav-logo-mark ниже), а приблизительная ширина всего блока
   .nav-menu-toggle справа (аватар+имя+стрелка+padding, обычно
   ~140-160px) - именно ЭТА ширина колонки и держит центральный блок
   кнопок по центру всей панели (см. комментарий у .page-nav выше про
   grid-template-columns: 1fr auto 1fr): если бы тут была голая ширина
   самого кружка (~40px), при обычной длине имени пользователя правая
   колонка оказалась бы заметно шире левой, и центральный блок сместился
   бы влево от истинного центра. Подгонка "на глаз" под типичную длину
   имени, а не точный расчёт - для точного пришлось бы измерять реальную
   ширину .nav-menu-toggle через JS на каждую загрузку/ресайз, что явно
   избыточно ради декоративной заглушки. aria-hidden - без реального
   смысла, пока не появится настоящий логотип.

   grid-column: 1 (тут и у .page-nav-links/.page-nav-right ниже) - явно, а
   не авто-расстановкой: без этого, стоит .page-nav-logo стать display:none
   (см. медиа-запрос ниже - на узких экранах логотип прячется первым),
   оставшиеся два элемента авто-расставились бы в колонки 1 и 2 вместо 2 и
   3, и .page-nav-links прижался бы к левому краю панели вместо своего
   места. Явное указание держит оба элемента в отведённых им колонках
   независимо от того, виден логотип или нет. */
.page-nav-logo {
  grid-column: 1;
  min-width: 150px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-self: start;
}

/* Сам видимый кружок - фиксированного размера независимо от ширины-бюджета
   родителя выше, тот же градиент, что и у .nav-menu-avatar справа, тем же
   визуальным весом. */
.page-nav-logo-mark {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.page-nav-logo-mark svg {
  width: 20px;
  height: 20px;
}

/* По запросу ("если места под кнопки хватать не будет... скрывать логотип,
   чтобы освободить больше места") - .container не ограничен max-width (см.
   styles.css), так что .page-nav растягивается почти на всю ширину
   viewport, и в промежутке между "мобильным" вырезом (max-width: 600px,
   ниже которого .page-nav вообще уступает место .bottom-nav) и полноразмерным
   десктопом кнопки по умолчанию не переносятся и не сжимаются
   (.page-nav-links - display:flex без wrap).

   Раньше здесь была голая CSS-граница (@media max-width:900px) - но
   подобранная на глаз ширина viewport'а не учитывает то, что и переполнить
   панель может не только блок кнопок, но и сам переключатель профиля справа
   (явный follow-up ask: "перестать помещаться может ещё и профиль... надо
   скрывать логотип, если уже для профиля не хватает места") - его ширина
   зависит от длины имени пользователя, которая не связана с viewport'ом
   никак: у кого-то короткое имя, и 900px-граница вообще не нужна, у кого-то
   длинное - панель переполняется заметно раньше. .page-nav-tight (см.
   nav-index.js's initPageNavLogoVisibility) ставится/снимается по РЕАЛЬНОМУ
   переполнению (scrollWidth > clientWidth), а не по угаданной ширине -
   срабатывает одинаково корректно вне зависимости от того, кто именно
   виноват в тесноте, кнопки или профиль. Логотип - самый безопасный
   кандидат на скрытие первым: чисто декоративная заглушка, в отличие от
   самих кнопок навигации или переключателя профиля, которые обязаны
   остаться доступными. */
.page-nav.page-nav-tight .page-nav-logo {
  display: none;
}

/* Оборачивает .nav-menu (или анонимную ссылку "Войти") в один предсказуемый
   grid-элемент третьей (1fr) колонки - без этой обёртки пришлось бы отдельно
   выставлять justify-self двум разным вариантам разметки ({% if
   user.is_authenticated %}). justify-self: end прижимает содержимое к
   правому краю этой растянутой на всё оставшееся место колонки - сама
   ширина колонки при этом всё равно равна левой (см. .page-nav выше), что и
   держит центральный блок кнопок по центру. */
.page-nav-right {
  grid-column: 3;
  justify-self: end;
}

.page-nav-links {
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: 4px;
  justify-self: center;
}

.page-nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: 12px;
  color: var(--text-secondary);
  text-decoration: none;
  font-weight: 500;
  transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

/* По запросу ("при наведении... чуть-чуть увеличивать или приподнимать") -
   лёгкий подъём (translateY), а не scale(): пункты стоят вплотную (gap: 4px),
   и увеличение по ширине/высоте рисковало бы наезжать на соседние кнопки в
   и без того тесном ряду, тогда как сдвиг вверх с тенью читается как
   "приподнялась" и не задевает соседей вообще. */
.page-nav-item:hover {
  background-color: rgba(var(--overlay-rgb), 0.06);
  color: var(--text-primary);
  transform: translateY(-2px);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
}

.page-nav-item:active {
  transform: scale(0.97);
}

.page-nav-item.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  color: white;
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
}

.page-nav-icon svg {
  width: 19px;
  height: 19px;
  display: block;
}

/*****************************
       Nav dropdown menu
******************************/
.nav-menu {
  position: relative;
}

.nav-menu-toggle {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px 6px 6px;
  border-radius: 20px;
  background-color: rgba(var(--overlay-rgb), 0.06);
  transition: background-color 0.15s ease;
}

.nav-menu-toggle:hover {
  background-color: rgba(var(--overlay-rgb), 0.12);
}

.nav-menu-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  color: white;
  font-size: 0.85em;
  overflow: hidden;
  flex-shrink: 0;
}

.nav-menu-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Красный значок с числом неподтверждённых приглашений (см.
   pending_participants_count) - на десктопе на самой кнопке-переключателе
   (пункт "Профиль" внутри закрытого по умолчанию меню никто бы не увидел),
   на мобильном - вариант поменьше поверх иконки "Профиль" в .bottom-nav-icon
   (см. .nav-badge-bottom ниже), которая и так всегда на экране. */
.nav-badge {
  position: absolute;
  top: -6px;
  right: -8px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background-color: var(--color-danger);
  color: white;
  font-size: 0.7em;
  font-weight: bold;
  line-height: 16px;
  text-align: center;
}

.bottom-nav-icon {
  position: relative;
}

.nav-badge-bottom {
  top: -4px;
  right: -6px;
}

.nav-menu-caret {
  font-size: 0.75em;
  color: var(--text-secondary);
}

/* .bottom-nav-panel (нижняя мобильная панель "Ещё") использует тот же вид,
   что и десктопный .nav-menu-dropdown, но это два независимых элемента - см.
   комментарий в schedule/_nav.html про то, почему не общий переиспользуемый. */
.nav-menu-dropdown,
.bottom-nav-panel {
  z-index: 1500;
  min-width: 180px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px;
  background-color: var(--bg-surface);
  border: 1px solid rgba(var(--overlay-rgb), 0.08);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.nav-menu-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
}

.nav-menu-dropdown.hidden {
  display: none;
}

/* .bottom-nav-panel's открытие/закрытие - явный ask ("UI/UX панели Ещё") -
   анимируется через opacity/transform, а не голый display:none/flex, как у
   .nav-menu-dropdown выше (десктопный вариант этой правкой не затронут).
   display остаётся flex всегда - на видимость это не влияет, раз сама
   панель уже position:fixed и не участвует в потоке документа, зато делает
   переход между состояниями анимируемым (display вообще нельзя
   плавно интерполировать). pointer-events: none в скрытом состоянии - без
   него невидимая, но всё ещё "присутствующая" панель перехватывала бы клики
   поверх остального контента страницы. */
.bottom-nav-panel {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.bottom-nav-panel.hidden {
  opacity: 0;
  /* 100%, не небольшой сдвиг, как раньше - раз панель теперь настоящий
     bottom sheet, выезжающий от нижнего края экрана (см. её же позиционирование
     ниже в мобильном media query), скрытое состояние должно быть полностью
     под этим краем, а не просто чуть смещённым вниз. */
  transform: translateY(100%);
  pointer-events: none;
}

.nav-menu-dropdown a,
.nav-menu-dropdown button,
.bottom-nav-panel a,
.bottom-nav-panel button {
  text-align: left;
  color: var(--text-primary);
  text-decoration: none;
  background: transparent;
  border: none;
  padding: 8px 10px;
  border-radius: 6px;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

/* Иконка+подпись(+бейдж) в ряд - только в мобильной панели "Ещё" (десктопный
   .nav-menu-dropdown остаётся текстовым списком без изменений, как и был -
   явный ask был именно про мобильное представление). */
.bottom-nav-panel a,
.bottom-nav-panel button {
  display: flex;
  align-items: center;
  gap: 10px;
}

.bottom-nav-panel-icon {
  display: flex;
  flex-shrink: 0;
}

.bottom-nav-panel-icon svg {
  width: 18px;
  height: 18px;
}

.bottom-nav-panel-label {
  flex: 1;
}

/* Тот же цвет/вес, что и у .nav-badge, но не absolute - сидит в конце
   строки как обычный inline-элемент flex-ряда, а не поверх иконки в углу
   (тому .nav-badge и служит everywhere else - см. .nav-badge-bottom на
   самой кнопке "Ещё" чуть выше). Отдельный класс, а не переиспользование
   .nav-badge как есть, специально чтобы не тащить туда лишний
   position:absolute. */
.bottom-nav-panel-badge {
  flex-shrink: 0;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background-color: var(--color-danger);
  color: white;
  font-size: 0.72em;
  font-weight: bold;
  line-height: 18px;
  text-align: center;
}

/* Разделитель между смысловыми группами пунктов (уведомления/приглашения -
   навигация - данные) и перед "Выйти" - явный ask, чтобы деструктивное
   действие визуально не сливалось с обычными пунктами списка над ним. */
.bottom-nav-panel-divider {
  height: 1px;
  margin: 4px 6px;
  background-color: rgba(var(--overlay-rgb), 0.1);
}

.nav-menu-dropdown a:hover,
.nav-menu-dropdown button:hover,
.bottom-nav-panel a:hover,
.bottom-nav-panel button:hover {
  background-color: rgba(var(--overlay-rgb), 0.08);
  transform: none;
  box-shadow: none;
}

.nav-menu-dropdown a.active,
.bottom-nav-panel a.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
}

.nav-menu-logout {
  color: var(--text-danger) !important;
}

.bottom-nav {
  display: none;
}

/* Десктопный дефолт - тот же приём, что и у самой .bottom-nav (display:none
   тут, display:block внутри мобильного media query ниже): подложка нужна
   только вместе с мобильной шторкой "Ещё", которой на десктопе не бывает. */
.bottom-nav-backdrop {
  display: none;
}

/* РЕАЛЬНЫЙ найденный баг ("гигантский отступ сверху в ПК-версии профиля") -
   общее правило .nav-menu-dropdown/.bottom-nav-panel выше (см.
   "display остаётся flex всегда" в его же комментарии) намеренно не
   трогает display в .hidden-состоянии - но это рассуждение молча
   предполагало, что .bottom-nav-panel уже position:fixed (вне потока
   документа) в любой момент, когда оно вообще может быть видимым. На
   самом деле position:fixed для неё задаётся ТОЛЬКО внутри мобильного
   media query ниже - на десктопе она как была, так и осталась обычным,
   участвующим в потоке document flow flex-контейнером. В сочетании с
   display:flex, унаследованным от общего правила, и классом hidden,
   который на неё не влияет (opacity/transform, не display) - невидимая,
   но полноразмерная панель "Ещё" (8+ пунктов меню) вставала прямо в
   поток страницы на каждой странице (то есть везде, см. _nav.html),
   просто ниже основной .page-nav и перед
   остальным содержимым - настоящая причина видимого "гигантского" зазора,
   не декоративная недоработка вёрстки. Тот же "display:none по
   умолчанию, display:flex только внутри мобильного media query" приём,
   что и у .bottom-nav/.bottom-nav-backdrop прямо здесь же. */
.bottom-nav-panel {
  display: none;
}

.auth-banner {
  background-color: rgba(var(--danger-rgb), 0.15);
  border: 1px solid rgba(var(--danger-rgb), 0.4);
  color: var(--text-danger);
  padding: 10px 15px;
  border-radius: 6px;
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.auth-banner a {
  color: var(--text-primary);
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  padding: 5px 14px;
  border-radius: 16px;
  text-decoration: none;
  font-weight: 500;
  white-space: nowrap;
  transition: transform 0.15s ease, filter 0.15s ease;
  display: inline-block;
}

.auth-banner a:hover {
  filter: brightness(1.1);
  transform: translateY(-1px);
}

/* django.contrib.messages - плавающий тост в правом верхнем углу
   (см. FlashMessages.js/schedule/_flash_messages.html), а не строка в
   потоке страницы поверх меню - изначально message.tags покрывал только
   ошибки валидации файла (profile_view/upload_event_image, см. CLAUDE.md
   'Image uploads'), позже сюда же добавились обычные success-подтверждения
   вида "Настройки сохранены", для которых строка над навигацией читалась
   как нежеланная дописка, а не мгновенная обратная связь на только что
   выполненное действие. position: fixed - поэтому не важно, куда именно в
   разметке шаблона попадает {% include 'schedule/_flash_messages.html' %},
   тост всё равно всплывает поверх всего. */
.flash-messages {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 3000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(360px, calc(100vw - 24px));
  pointer-events: none;
}

.flash-message {
  padding: 10px 15px;
  border-radius: 6px;
  margin: 0;
  background-color: var(--bg-surface-3);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
  cursor: pointer;
  pointer-events: auto;
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.25s ease, transform 0.25s ease;
}

/* Закрытие по клику/таймеру (FlashMessages.js) - class добавляется, затем
   сам элемент удаляется из DOM по transitionend, а не сразу, чтобы успело
   доиграть затухание. */
.flash-message.flash-message-hide {
  opacity: 0;
  transform: translateY(-8px);
}

.flash-message.flash-success {
  background-color: rgba(var(--success-rgb), 0.15);
  border: 1px solid rgba(var(--success-rgb), 0.4);
  color: var(--color-success);
}

.flash-message.flash-error {
  background-color: rgba(var(--danger-rgb), 0.15);
  border: 1px solid rgba(var(--danger-rgb), 0.4);
  color: var(--text-danger);
}

/*****************************
       Auth page (login)
******************************/
.auth-box {
  max-width: 320px;
  margin: 10vh auto;
  padding: 25px;
}

.auth-box h2 {
  margin-top: 0;
  text-align: center;
}

.auth-box label {
  display: block;
  margin-bottom: 12px;
  font-size: 0.9em;
}

.auth-box input {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 8px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
}

.auth-box button {
  width: 100%;
  padding: 10px;
  border: none;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  color: white;
  font-weight: 500;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
}

.auth-box button:hover {
  filter: brightness(1.1);
  transform: translateY(-1px);
}

.auth-box button:active {
  transform: translateY(0);
}

/* Страница 404 (schedule/templates/404.html - вне namespace приложения,
   Django ищет её по буквальному имени '404.html' в любом APP_DIRS, см.
   CLAUDE.md) - тот же .auth-box-подобный центрированный минималистичный вид,
   без навигации (страница может быть показана и анонимному пользователю по
   любой битой ссылке). */
.error-page-card {
  max-width: 340px;
  margin: 12vh auto;
  padding: 30px 25px;
  text-align: center;
}

.error-page-icon {
  width: 48px;
  height: 48px;
  color: var(--accent);
  margin-bottom: 8px;
}

.error-page-code {
  font-size: 2.4em;
  font-weight: 700;
  color: var(--accent);
}

.error-page-title {
  margin: 4px 0 10px;
}

.error-page-text {
  color: var(--text-muted);
  font-size: 0.9em;
  margin-bottom: 20px;
}

.error-page-link {
  display: inline-block;
  padding: 10px 22px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  color: white;
  font-weight: 500;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
  transition: transform 0.15s ease, filter 0.15s ease;
}

.error-page-link:hover {
  filter: brightness(1.1);
  transform: translateY(-1px);
}

.auth-error {
  color: var(--text-danger);
  font-size: 0.9em;
}

.auth-legal-links {
  margin-top: 14px;
  text-align: center;
  font-size: 0.8em;
  color: var(--text-tertiary);
}

.auth-legal-links a {
  color: var(--text-tertiary);
  text-decoration: underline;
}

/*****************************
       Day header + add-event button
******************************/
.planner thead th.today {
  background-color: var(--today-highlight);
}

/* Кликабельна целиком - клик открывает создание события на этот день (см.
   Planner.initEventButtons), отдельной кнопки "+" в шапке недели больше нет. */
.day-name {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  border-radius: 4px;
  transition: background-color 0.15s ease;
}

.day-name:hover {
  background-color: rgba(var(--overlay-rgb), 0.15);
}

.add-event-btn {
  flex-shrink: 0;
  width: 1.4em;
  height: 1.4em;
  padding: 0;
  border: none;
  border-radius: 50%;
  background-color: rgba(var(--overlay-rgb), 0.2);
  color: white;
  cursor: pointer;
  /* SVG-крестик, а не текстовый символ "+" - у текстового глифа перекладина
     в некоторых шрифтах сидит не строго по центру строчного бокса, из-за
     чего кнопка на глаз выглядела смещённой в состоянии покоя (при
     scale(1.15) на hover это визуально сглаживалось). flex тут по-прежнему
     нужен - кладёт саму svg точно в центр круга. */
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
}

.add-event-btn svg {
  width: 60%;
  height: 60%;
}

.add-event-btn:hover {
  background-color: var(--accent-solid);
  transform: scale(1.15);
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.6);
}

.add-event-btn:active {
  transform: scale(1);
}

/*****************************
       Events on the week grid
******************************/
/* Чередующиеся полосы по 6 часов (посветлее/потемнее) - помогают на глаз
   ориентироваться, какая часть суток сейчас перед глазами, не считая
   каждый раз подписи слева. --row-band выставляется на <tr> (48 строк по
   получасу - см. schedule_view/timestamps в views.py, ровно 12 строк на
   6-часовой отрезок) и наследуется всеми <td> внутри неё - дешевле, чем
   расписывать background-color на каждую ячейку через длинные nth-child
   цепочки. .cell унаследует значение через var(), первая/последняя колонки
   (подписи времени) остаются как были - им background-color задаётся
   отдельным более специфичным правилом (transparent), variable сюда не
   попадает вообще. Тёмная полоса (--bg-surface-3) - тот же цвет, что был у
   ячейки до этой функциональности (годами проверенный на сочетаемость со
   всем остальным в приложении в тёмной теме) - половина палитры так вообще
   не новая; светлая (--bg-surface-4) - единственный по-настоящему новый
   оттенок, изначально подобранный (в тёмной теме) с большим запасом яркости
   и от .container (--bg-page), и от .week-card/"Card" (--bg-surface), и от
   шапки дней (--bg-header), чтобы таблица не "растворялась" ни в одном из
   них (более ранняя версия дважды промахивалась мимо этого) - у светлой темы
   (см. :root[data-theme="light"] в styles.css) свой набор значений тех же
   переменных с той же логикой контраста, просто с другим знаком. Полосы
   одинаковые на десктопе и мобильном - никакого отдельного мобильного
   набора цветов нет и не должно быть. */
.planner tbody tr:nth-child(n+1):nth-child(-n+12) {
  --row-band: var(--bg-surface-4);
}
.planner tbody tr:nth-child(n+13):nth-child(-n+24) {
  --row-band: var(--bg-surface-3);
}
.planner tbody tr:nth-child(n+25):nth-child(-n+36) {
  --row-band: var(--bg-surface-4);
}
.planner tbody tr:nth-child(n+37):nth-child(-n+48) {
  --row-band: var(--bg-surface-3);
}

.planner td.cell {
  background-color: var(--row-band, var(--bg-surface-3));
}

/* Единый контур события вместо набора отдельно закрашенных получасовых
   ячеек: у всех .has-event сняты собственные скругления (border-radius: 0),
   скругляются только верх у первой ячейки события (.event-start) и низ у
   последней (.event-end, см. Planner.renderEvents) - в один час (одна
   ячейка) получает оба класса разом, поэтому скругляется со всех сторон.
   Верх/низ у "средних" ячеек намеренно НЕ трогаются (остаются transparent,
   как у обычной ячейки) - более ранняя версия красила border-color на всех
   четырёх сторонах сразу, из-за чего между каждой парой соседних получасовых
   ячеек ОДНОГО И ТОГО ЖЕ события появлялась тонкая горизонтальная полоска
   (унаследованная 0.15em top/bottom граница переставала быть прозрачной) -
   вместо цельного блока получалась стопка ломтиков, ровно то, чего этот
   приём должен был избежать. Левая/правая граница событию отдельно не
   красится вовсе - тот же вертикальный разделитель дней, что и у обычных
   ячеек, и так достаточно виден на закрашенном фоне события. top/bottom
   явно transparent - на мобильном (см. media-запрос ниже) ВСЕ ячейки получают
   свой собственный border-bottom для разделения строк ("Every 4 lines"),
   который иначе точно так же просвечивал бы между получасовыми кусками
   ОДНОГО события; .event-start/.event-end ниже переопределяют это явно, для
   всех промежуточных ячеек любого события граница остаётся невидимой на
   любой ширине экрана. Селектор включает .cell (а не только .has-event) -
   без этого специфичность (2 класса) была бы НИЖЕ, чем у
   ".planner tbody tr:nth-child(4n) td" (тоже мобильный, отмечает каждый
   2-часовой рубеж, 2 класса + 3 типа), и тот продолжал бы просвечивать
   каждые 4 строки прямо посреди события. !important нужен, чтобы цвет
   события не перекрывался цветом категории, у которого тоже стоит
   !important. */
.planner td.cell.has-event {
  background-color: var(--event-color) !important;
  border-radius: 0;
  border-top-color: transparent;
  border-bottom-color: transparent;
}

.planner td.has-event.event-start {
  border-top-left-radius: 0.6em;
  border-top-right-radius: 0.6em;
  border-top-width: 0.12em;
  border-top-color: rgba(var(--overlay-rgb), 0.45);
}

.planner td.has-event.event-end {
  border-bottom-left-radius: 0.6em;
  border-bottom-right-radius: 0.6em;
  border-bottom-width: 0.12em;
  border-bottom-color: rgba(var(--overlay-rgb), 0.45);
}

/* Несколько событий, пересекающихся по времени, делят одну ячейку (см.
   Planner.paintCell) - диагональная "тельняшка" из цветов всех участников
   (--overlap-colors собирается в JS уже готовым списком цветовых точек
   остановки, см. paintCell) вместо попытки показать/скруглить каждое из них
   по отдельности, что не имело бы смысла при разных start/end. Селектор
   включает .cell (как и .has-event выше) - без этого специфичность (3 класса)
   лишь сравнялась бы с .planner td.cell.has-event (тоже 3 класса, тоже
   !important), и победа зависела бы от порядка объявления в файле, а не была
   бы гарантирована явно. */
.planner td.cell.has-event.has-overlap {
  background: repeating-linear-gradient(135deg, var(--overlap-colors)) !important;
  border-radius: 0;
}

.event-label-overlap .event-label-text {
  font-weight: 600;
}

.event-label {
  position: absolute;
  top: 0;
  left: 0.2em;
  right: 0.2em;
  display: flex;
  align-items: center;
  gap: 2px;
  font-size: 0.7em;
  color: white;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.9);
  overflow: hidden;
  pointer-events: none;
}

.event-label-text {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Попап выбора события при клике по ячейке с пересекающимися по времени
   событиями (EventModal.openOverlapPicker) - список кликабельных строк,
   тот же .category-list-swatch (цветная точка), что и у категорий/тегов. */
.overlap-picker-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 12px 0;
  max-height: 300px;
  overflow-y: auto;
}

.overlap-picker-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  border-radius: 6px;
  background-color: rgba(var(--overlay-rgb), 0.05);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.overlap-picker-row:hover {
  background-color: rgba(var(--overlay-rgb), 0.12);
}

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

/* Значки повтора/группового/завершённого события (см. EventBadges.js) -
   тот же .event-badge-icon, что и на чипах месяца/подписях недели, просто
   в общем построчном flex-контейнере (.overlap-picker-row), а не внутри
   .event-chip-time/.event-label. */
.overlap-picker-row-badges {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}

/* Попап поиска событий (SearchModal.js), открывается той же кнопкой-триггером,
   что и Мои категории/Мои шаблоны/Мои места - шире обычного .modal-box (320px),
   поскольку строка результата несёт заметно больше текста (название + дата/
   время/категория) чем те списки. */
.search-modal-box {
  width: 420px;
}

.search-input {
  display: block;
  width: 100%;
  margin: 4px 0 12px;
  padding: 8px 10px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
}

.search-results-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 320px;
  overflow-y: auto;
}

.search-results-empty {
  padding: 10px 4px;
  color: rgba(var(--text-primary-rgb), 0.6);
  font-size: 0.9em;
  text-align: center;
}

/* Та же строка-кнопка, что и .overlap-picker-row, но настоящая ссылка
   (переход на /?date=<дата>, см. SearchModal.renderResults) - текстовое
   оформление ссылки по умолчанию (подчёркивание/синий цвет) тут не нужно. */
.search-result-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border-radius: 6px;
  background-color: rgba(var(--overlay-rgb), 0.05);
  color: inherit;
  text-decoration: none;
  transition: background-color 0.15s ease;
}

.search-result-row:hover {
  background-color: rgba(var(--overlay-rgb), 0.12);
}

.search-result-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.search-result-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-result-meta {
  font-size: 0.8em;
  color: rgba(var(--text-primary-rgb), 0.6);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Линия "сейчас" в ячейке текущего получаса сегодняшней колонки (Planner.
   renderCurrentTimeLine). pointer-events:none обязателен - как и у Tooltip,
   иначе она может стать event.target прямо во время драг-выделения и
   сломать расчёт ячейки под курсором/пальцем. */
.current-time-line {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background-color: var(--color-danger);
  z-index: 5;
  pointer-events: none;
}

.current-time-line::before {
  content: '';
  position: absolute;
  left: -3px;
  top: -3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--color-danger);
}

/*****************************
       Event modals (create/details)
******************************/
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
}

.modal-overlay.hidden {
  display: none;
}

/* Онбординг-тур (OnboardingTour.js, см. CLAUDE.md "Онбординг-тур") -
   намеренно НЕ .modal-overlay: тур не блокирует страницу под собой
   (pointer-events: none на самом оверлее/спотлайте), только карточка с
   текстом кликабельна - см. комментарий в OnboardingTour.js о том, почему
   это не modal в обычном смысле. */
.onboarding-tour-overlay {
  position: fixed;
  inset: 0;
  z-index: 2500;
  pointer-events: none;
}

.onboarding-tour-overlay.hidden {
  display: none;
}

.onboarding-tour-spotlight {
  position: fixed;
  border-radius: 8px;
  /* Тот же приём, что у популярных библиотек туров: не оверлей рисует
     тёмный фон и "дырку" под целевой элемент, а сам целевой прямоугольник
     рисует огромную тень вовне себя - визуально получается затемнение
     всего экрана вокруг него, без масок/clip-path. */
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.65);
  transition: top 0.2s ease, left 0.2s ease, width 0.2s ease, height 0.2s ease;
}

.onboarding-tour-card {
  position: fixed;
  width: 280px;
  padding: 16px 18px;
  background-color: var(--bg-surface);
  border: 1px solid rgba(var(--overlay-rgb), 0.12);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  color: var(--text-primary);
  pointer-events: auto;
}

.onboarding-tour-title {
  margin: 0 0 8px;
  font-size: 1.05em;
}

.onboarding-tour-text {
  margin: 0 0 14px;
  font-size: 0.9em;
  color: var(--text-secondary);
  line-height: 1.4;
}

.onboarding-tour-actions {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}

/* justify-content: space-between вместо .modal-actions' flex-end - тут
   ровно 2 кнопки с разным весом действия (пропустить/продолжить), и им
   имеет смысл разъехаться по разным краям, а не собраться в правом углу.
   Базовый вид кнопки повторяет .modal-actions button вручную, раз этот ряд
   сознательно не переиспользует сам класс .modal-actions. */
.onboarding-tour-actions button {
  padding: 8px 16px;
  border: 1px solid rgba(var(--overlay-rgb), 0.1);
  border-radius: 10px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  cursor: pointer;
  font-weight: 500;
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}

.onboarding-tour-skip {
  background: transparent !important;
  border-color: transparent !important;
  color: var(--text-faint);
  box-shadow: none !important;
}

.modal-box {
  padding: 20px 25px;
  max-width: 90vw;
  max-height: 85vh;
  overflow-y: auto;
  /* Тот же тонкий скроллбар в цвет темы, что и у .month-day-events (см. ниже)
     - раньше здесь оставался жирный нативный скроллбар браузера/ОС, не
     подхватывающий тему, единственное такое место среди всех прокручиваемых
     блоков приложения. Общий для ВСЕХ попапов (EventModal и любой другой на
     .modal-box), не только "Новое событие" - у него просто чаще всего
     реально доходит до прокрутки из-за количества полей формы. */
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--overlay-rgb), 0.35) transparent;
}

.modal-box h3 {
  margin-top: 0;
}

.modal-box label {
  display: block;
  margin-bottom: 12px;
  font-size: 0.9em;
  font-weight: 500;
}

.modal-box input[type="text"],
.modal-box input[type="date"],
.modal-box input[type="time"],
.modal-box input[type="number"],
.modal-box input[type="url"],
.modal-box select,
.modal-box textarea {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 6px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
}

.modal-box input[type="color"] {
  display: block;
  margin-top: 4px;
  width: 48px;
  height: 32px;
  padding: 0;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: transparent;
}

.event-category-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}

.event-category-row select {
  margin-top: 0;
  flex: 1;
}

.event-category-swatch {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid rgba(var(--overlay-rgb), 0.3);
}


.category-list,
.place-list {
  max-height: 220px;
  overflow-y: auto;
  overflow-x: hidden;
  margin-bottom: 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.category-list-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 6px;
  border-radius: 6px;
  background-color: rgba(var(--overlay-rgb), 0.04);
}

.category-list-swatch {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid rgba(var(--overlay-rgb), 0.3);
}

.category-list-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.category-list-edit,
.category-list-delete {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background-color: transparent;
  color: var(--text-secondary);
  font-size: 1.1em;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.category-list-edit:hover {
  background-color: rgba(var(--accent-solid-rgb), 0.25);
  color: var(--accent);
}

.category-list-delete:hover {
  background-color: rgba(var(--danger-rgb), 0.25);
  color: var(--text-danger);
}

/* Строка категории, которую сейчас правят в форме ниже - тот же акцент,
   что и .today/.active в других местах приложения. */
.category-list-row.editing {
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* Фильтр событий по категориям (CategoryFilter.js) - переиспользует
   .category-list/-row/-swatch/-name как есть (см. CLAUDE.md - тот же
   прецедент, что и у PlaceModal), только .category-list-row тут <label> с
   чекбоксом внутри вместо строки с кнопками редактирования/удаления -
   явный cursor:pointer, раз назначение по умолчанию для <label> не
   гарантировано во всех браузерах. .category-filter-actions - "Показать
   все"/"Скрыть все" читаются как панель инструментов НАД списком, а не
   финальный ряд действий попапа, поэтому выравнивание по левому краю, а
   не унаследованное от .modal-actions justify-content: flex-end. */
.category-filter-row {
  cursor: pointer;
}

.category-filter-actions {
  justify-content: flex-start;
  margin-top: 0;
  margin-bottom: 10px;
}

.category-cancel-edit.hidden {
  display: none;
}

/*****************************
       Event templates (шаблоны событий)
******************************/
.template-list {
  max-height: 220px;
  overflow-y: auto;
  overflow-x: hidden;
  margin-bottom: 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.template-list-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 6px;
  border-radius: 6px;
  background-color: rgba(var(--overlay-rgb), 0.04);
}

.template-list-row.editing {
  box-shadow: inset 0 0 0 1px var(--accent);
}

.template-list-swatch {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid rgba(var(--overlay-rgb), 0.3);
}

.template-list-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.template-list-apply {
  flex-shrink: 0;
  padding: 4px 10px;
  border: 1px solid rgba(var(--accent-rgb), 0.4);
  border-radius: 14px;
  background-color: rgba(var(--accent-rgb), 0.15);
  color: var(--accent);
  font-size: 0.8em;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.template-list-apply:hover {
  background-color: rgba(var(--accent-rgb), 0.3);
}

.template-list-edit,
.template-list-delete {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background-color: transparent;
  color: var(--text-secondary);
  font-size: 1.1em;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.template-list-edit:hover {
  background-color: rgba(var(--accent-solid-rgb), 0.25);
  color: var(--accent);
}

.template-list-delete:hover {
  background-color: rgba(var(--danger-rgb), 0.25);
  color: var(--text-danger);
}

.template-cancel-edit.hidden {
  display: none;
}

/* Плавающий баннер режима "проставить шаблон на несколько дней" -
   MonthView.buildApplyBanner. Поверх сетки месяца, но ниже модалок. */
.template-apply-banner {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 1300;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-radius: 24px;
  background-color: var(--bg-surface);
  border: 1px solid rgba(var(--overlay-rgb), 0.12);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  color: var(--text-primary);
  font-size: 0.9em;
  max-width: calc(100vw - 24px);
}

.template-apply-banner.hidden {
  display: none;
}

.template-apply-banner-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.template-apply-banner button {
  flex-shrink: 0;
  padding: 6px 12px;
  border: 1px solid rgba(var(--overlay-rgb), 0.1);
  border-radius: 14px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  cursor: pointer;
  font: inherit;
  font-size: 0.9em;
}

.template-apply-banner .template-apply-done {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  border-color: transparent;
}

/* Ячейка, кликабельная для выбора дня в режиме проставления - курсор явно
   показывает, что клик здесь означает "выбрать день", а не "открыть/создать
   событие", даже до самого первого клика. */
.month-day.template-apply-selectable {
  cursor: pointer;
}

.month-day.template-apply-selected {
  box-shadow: inset 0 0 0 2px var(--accent);
  background-color: rgba(var(--accent-rgb), 0.15);
}

.modal-box input[type="range"] {
  width: calc(100% - 30px);
  vertical-align: middle;
  accent-color: var(--accent-solid);
}

.event-priority-value {
  display: inline-block;
  width: 20px;
  text-align: center;
  font-weight: bold;
}

/* При создании дата уже видна в самом заголовке формы ("Новое событие -
   12.08.2026", см. EventModal.open) - отдельного read-only поля под неё не
   нужно. При редактировании дата, наоборот, изменяемая - тогда виден
   настоящий <input type="date"> (.event-date-field), скрытый по умолчанию
   для создания. */
.event-date-field.hidden {
  display: none;
}

/* Начало/Конец в одну строку для экономии места - тот же приём "два поля
   рядом, а не друг под другом", что и у .event-shift ниже. */
.event-time-row {
  display: flex;
  gap: 10px;
  margin-bottom: 12px;
}

.event-time-row label {
  flex: 1;
  margin-bottom: 0;
}

/* Повтор изначально свёрнут в одну кнопку - раскрывать целую форму с select
   и (для части значений) ещё и дополнительными полями ради события, которое
   в большинстве случаев вообще не повторяется, было лишним. Клик по кнопке -
   разовое раскрытие (кнопка прячется насовсем до следующего открытия формы),
   а не переключатель туда-обратно: если после раскрытия передумать, в самом
   select'е для этого уже есть свой пункт "Никогда" (первый в списке) - он же
   и есть "без повтора" по умолчанию, если строку вообще не открывали (см.
   REPEAT_OPTIONS/submit() в EventModal.js). */

.event-repeat-row .event-weekdays,
.event-repeat-row .event-shift,
.event-repeat-row .event-repeat-until-field {
  margin-top: 12px;
}

/* Категория, Место и Теги свёрнуты за той же схемой, что и Повтор выше -
   одна и та же логика "разово раскрыть по клику, вернуться к умолчанию/
   пустому значению можно и без отдельной кнопки-обратно" (см. комментарий
   у .event-repeat-toggle выше и EventModal.js). Категория при создании
   раскрывать не обязательно вовсе - по умолчанию и так встаёт
   "Повседневное" (см. EventModal.js's open()), кнопка тут только на случай,
   если организатор захочет её сменить сразу. Пригласить участников - НЕ
   часть этой же схемы (кнопка-раскрытие для неё убрана, см.
   .event-invite-section ниже) - там показ/скрытие уже само по себе
   означает "доступно/недоступно", требовать ещё один клик поверх этого
   было лишним шагом. */
.event-category-toggle,
.event-place-toggle,
.event-tags-toggle,
.event-repeat-toggle {
  margin-bottom: 12px;
  padding: 8px 8px;
  font-weight: normal;
}

.event-category-toggle.hidden,
.event-place-toggle.hidden,
.event-tags-toggle.hidden,
.event-repeat-toggle.hidden {
  display: none;
}

.event-category-row.hidden,
.event-place-row.hidden,
.event-tags-row.hidden,
.event-repeat-row.hidden {
  display: none;
}

.event-category-row,
.event-place-row,
.event-tags-row,
.event-repeat-row {
  margin-bottom: 12px;
}

/* Пригласить участников показывается/прячется сразу целиком (см.
   EventModal.js updateInviteVisibility) - никакой отдельной
   кнопки-раскрытия тут больше нет, а подпись "Пригласить участников" над
   списком убрана вовсе (явный запрос) - сам факт появления списка (только
   для группового события) уже достаточно объясняет, что это такое. */
.event-invite-section {
  margin-bottom: 12px;
}

.event-invite-section.hidden {
  display: none;
}

/* Внешняя ссылка (Discord/Zoom/...) - показывается/прячется сразу целиком,
   тот же приём, что и у .event-invite-section выше (см.
   EventModal.js updateExternalLinkVisibility) - только для группового
   события. */
.event-external-link-field.hidden {
  display: none;
}

/* Явные парные .hidden-правила (в этом кодбейзе нет общего .hidden { display: none }
   - см. CLAUDE.md "Push-уведомления") для трёх полей формы события, у
   которых их не было вовсе - реальный найденный баг: JS исправно навешивал/
   снимал класс hidden (см. updateRemindMeVisibility/updatePickTimeHintVisibility
   в EventModal.js и разметку .event-group-switch-locked-hint), но без этих
   правил класс ничего не менял визуально, так что все три элемента были
   видны всегда, независимо от условия - подсказка "Подобрать время" (только
   для нового группового события) показывалась и для личных, "Напоминать о
   событии" (только для личного) показывалось и для группового, а подсказка
   про неизменяемый тип события - вообще всегда, даже при создании. */
.event-group-switch-locked-hint.hidden,
.event-pick-time-hint.hidden,
.event-remind-me-field.hidden {
  display: none;
}

/* Пикер приглашения участников (см. EventModal.js renderInviteUI) - список
   групп, каждая со своим чекбоксом "выбрать всех" и вложенным списком
   чекбоксов по каждому участнику отдельно. max-height + свой тонкий
   скроллбар - тот же приём, что и у .month-day-events (см. CLAUDE.md), раз
   у пользователя с несколькими крупными группами список может оказаться
   длиннее, чем стоит показывать целиком внутри .modal-box (даже без
   фиксированной ширины у самого попапа - см. CLAUDE.md - высота всё равно
   ничем, кроме max-height/85vh, не ограничена сама по себе). */
.event-invite-list {
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid rgba(var(--overlay-rgb), 0.1);
  border-radius: 8px;
  padding: 8px;
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--overlay-rgb), 0.35) transparent;
}

.event-invite-list::-webkit-scrollbar {
  width: 6px;
}

.event-invite-list::-webkit-scrollbar-thumb {
  background-color: rgba(var(--overlay-rgb), 0.35);
  border-radius: 3px;
}

.event-invite-group {
  margin-bottom: 10px;
}

.event-invite-group:last-child {
  margin-bottom: 0;
}

/* Два класса в селекторе, не один - .modal-box label (класс+тип,
   специфичность 0,1,1) иначе перебивает одноклассовые .event-invite-group-header/
   .event-invite-member (0,1,0) своим display:block, ровно та же ловушка и то
   же решение, что и у .plan-form .file-input-native (см. CLAUDE.md "Styled
   file input"). */
.event-invite-list .event-invite-group-header {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  color: var(--text-primary);
  cursor: pointer;
}

.event-invite-group-members {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 4px 0 0 22px;
}

.event-invite-list .event-invite-member {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-secondary);
  font-size: 0.92em;
  cursor: pointer;
  font-weight: normal;
}

/* Переключатели дней недели для repeat='weekdays' (см. EventModal.js) -
   видны только когда выбран этот тип повтора. Тот же градиент, что и у
   .events-time-toggle-btn.active/.page-nav-item.active. */
.event-weekdays.hidden {
  display: none;
}

.event-weekdays {
  margin-bottom: 12px;
}

.event-weekdays-label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.9em;
  color: var(--text-secondary);
}

.event-weekdays-toggles {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.event-weekday-toggle {
  padding: 6px 10px;
  border: none;
  border-radius: 16px;
  background-color: rgba(var(--overlay-rgb), 0.08);
  color: var(--text-primary);
  cursor: pointer;
  font-size: 0.9em;
  transition: background-color 0.15s ease;
}

.event-weekday-toggle.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
}

/* Два числовых поля сменного графика (repeat='shift') - виден только для
   этого типа повтора, тот же принцип display:none, что и у .event-weekdays
   выше, просто рядом друг с другом, а не колонкой (у каждого свой короткий
   <label>, ставить их друг под другом растянуло бы форму без нужды). */
.event-shift.hidden {
  display: none;
}

.event-shift {
  display: flex;
  gap: 10px;
  margin-bottom: 12px;
}

.event-shift label {
  flex: 1;
  margin-bottom: 0;
}

/* "Повторять до" (Event.repeat_until) - виден для любого repeat кроме
   'none', тот же принцип, что и у .event-weekdays.hidden выше. */
.event-repeat-until-field.hidden {
  display: none;
}

/* Переключатель "Личное/Групповое" (Event.is_group_event) + блок "Кто может
   присоединиться" (restricted_to_group) - см. CLAUDE.md "Event kind".
   Скользящий двухпозиционный свитч вместо обычного чекбокса с подписью -
   вдохновлено примером в examples/cool-switch (не копия: тот - трёхпозиционный
   тарифный переключатель с вложенным под-свитчем и debug-панелью, тут
   нужен только простой personal/group булев тумблер, так что взята лишь
   сама идея "текст под скользящей заливкой, активная половина - светлая",
   без вложенной структуры и сторонних зависимостей). Настоящий <input
   type="checkbox" class="event-is-group"> остаётся источником истины (тот
   же .checked/.disabled API, которым уже пользуется весь остальной код
   этого файла) - просто визуально спрятан и заменён на .event-group-switch-track
   поверх, управляемый чистым CSS через :checked-селектор по соседям, без
   отдельного JS для самой анимации. */
.event-group-switch {
  position: relative;
  display: block;
  width: 100%;
  margin-bottom: 12px;
  cursor: pointer;
}

.event-group-switch .event-is-group {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.event-group-switch-track {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  width: 100%;
  height: 38px;
  padding: 3px;
  border-radius: 4px;
  background: var(--bg-inset);
  border: 1px solid rgba(var(--overlay-rgb), 0.12);
}

.event-is-group:focus-visible ~ .event-group-switch-track {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.event-group-switch-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: calc(50% - 3px);
  height: calc(100% - 6px);
  border-radius: 4px;
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
  /* Та же гладкая ease-кривая ускорения-с-замедлением, что даёт свитчу из
     примера его "дорогое" ощущение - обычный ease/linear выглядел заметно
     более резким при том же duration. */
  transition: transform 0.32s cubic-bezier(0.65, 0, 0.35, 1);
}

.event-is-group:checked ~ .event-group-switch-track .event-group-switch-thumb {
  transform: translateX(100%);
}

.event-group-switch-option {
  position: relative;
  z-index: 1;
  text-align: center;
  font-size: 0.9em;
  font-weight: 500;
  color: var(--text-faint);
  transition: color 0.32s ease;
  pointer-events: none;
}

.event-is-group:not(:checked) ~ .event-group-switch-track .event-group-switch-option:first-child,
.event-is-group:checked ~ .event-group-switch-track .event-group-switch-option:last-child {
  color: white;
}

.event-is-group:disabled ~ .event-group-switch-track {
  opacity: 0.55;
  cursor: not-allowed;
}

.event-group-restriction.hidden {
  display: none;
}

.event-group-restriction {
  margin: -4px 0 12px;
  padding-left: 4px;
}

.event-group-restriction-label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.9em;
  color: var(--text-secondary);
}

.event-group-restriction-option {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
  font-size: 0.9em;
  cursor: pointer;
}

/* Тот же чек-лист-пикер, что и .event-invite-list (те же CSS-классы, см.
   EventModal.js/renderRestrictedGroupList) - просто со своим отступом
   сверху, раз сидит отдельным блоком под обеими радио-опциями "Любая моя
   группа"/"Только группа", а не внутри одной из них. */
.event-restricted-group-list {
  margin-top: 6px;
}

.event-restricted-group-list.hidden {
  display: none;
}

/* Раньше пояснение к видимости было всегда видимым текстом под select'ом
   (.event-visibility-hint, .plan-empty) - теперь спрятано за
   кнопку-иконку .event-visibility-info-btn справа от подписи поля, см.
   EventModal.js updateVisibilityHint()/toggle-по-клику. .event-visibility-field
   нужен position:relative, чтобы всплывающая подсказка позиционировалась
   именно относительно этого поля, а не всей формы. */
.event-visibility-field {
  position: relative;
  margin-bottom: 12px;
}

.event-visibility-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 4px;
}

/* .modal-box label ставит остальным подписям полей 0.9em/500 - у этого
   поля подпись вынесена из <label> в обычный <span> (кнопка-иконка рядом
   не должна сама оборачиваться в <label>, см. EventModal.js), так что без
   этого явного повтора она наследовала бы обычный, куда более крупный
   базовый размер шрифта модалки. По той же причине сам select ниже
   раньше сидел внутри того же <label> и неявно наследовал те же 0.9em -
   вынесенный наружу, он тоже нуждается в этом явном повторе, иначе читался
   крупнее подписи над ним и всех остальных select/input в этой форме. */
.event-visibility-label-text {
  font-size: 0.9em;
  font-weight: 500;
}

.event-visibility-field select {
  font-size: 0.9em;
}

/* Тихая иконка-подсказка, а не отдельная кнопка - похожий приём на
   .event-comment-delete-btn (лёгкая подсветка только по наведению/фокусу,
   не постоянно видимая обводка .details-link), но с чуть заметным фоном и
   более контрастным цветом в состоянии покоя - совсем прозрачная и на
   --text-faint оказалась слишком незаметной, чтобы вообще найти взглядом.
   Итоговый размер (после пары неудачных промежуточных попыток) задан явно:
   кружок 20px, padding 2px (box-sizing: border-box, чтобы padding не
   раздувал сам кружок сверх 20px) - сам svg занимает 100% оставшегося
   содержимого (16px), а не фиксированный px-размер отдельно от контейнера. */
.event-visibility-info-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 20px;
  height: 20px;
  padding: 2px;
  border: none;
  border-radius: 50%;
  background-color: rgba(var(--overlay-rgb), 0.1);
  color: var(--text-secondary);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.event-visibility-info-btn:hover,
.event-visibility-info-btn:focus-visible {
  background-color: rgba(var(--accent-rgb), 0.15);
  color: var(--accent);
}

.event-visibility-info-btn svg {
  width: 100%;
  height: 100%;
}

/* Справа от кнопки-иконки (см. запрос пользователя), но внутри узкого
   .modal-box (уже без фиксированной ширины - см. CLAUDE.md, - но всё равно
   ограниченного max-width: 90vw и содержимым самой формы) выступать вправо
   за пределы поля всё равно особо некуда - поэтому подсказка растягивается
   на всю ширину поля и раскрывается вниз, а не вбок. */
.event-visibility-tooltip {
  position: absolute;
  top: 100%;
  right: 0;
  left: 0;
  z-index: 5;
  margin-top: 4px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid rgba(var(--overlay-rgb), 0.12);
  background-color: var(--bg-surface-3);
  color: var(--text-secondary);
  font-size: 0.85em;
  line-height: 1.4;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
}

.event-visibility-tooltip.hidden {
  display: none;
}

.modal-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 10px;
}

/* Заголовок попапа деталей события + иконка-ссылка на полную страницу
   события - раньше "Подробнее" был обычной кнопкой в .modal-actions, где при
   4-5 кнопках в ряд ей физически не хватало места и она обрезалась/вылезала
   за границы попапа (см. CLAUDE.md). Вынесена сюда, в отдельный ряд с
   заголовком - там всегда есть место, и это отдельное по смыслу действие
   ("уйти на другую страницу"), а не ещё одна кнопка среди
   Закрыть/Редактировать/Пропустить/Удалить. */
.details-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.details-header .details-title {
  margin: 0;
}

/* Ссылка на полную страницу события и кнопка закрытия попапа-предпросмотра -
   сгруппированы вместе, чтобы .details-header своим justify-content:
   space-between делил заголовок и эту пару строго на 2 элемента, а не
   разбрасывал три кнопки по всей ширине строки. */
.details-header-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

.details-link {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  color: var(--text-secondary);
  background-color: rgba(var(--overlay-rgb), 0.06);
  transition: background-color 0.15s ease, color 0.15s ease;
}

.details-link:hover {
  background-color: rgba(var(--accent-rgb), 0.25);
  color: var(--accent);
}

.details-link svg {
  width: 16px;
  height: 16px;
}

.modal-actions button {
  padding: 8px 16px;
  border: 1px solid rgba(var(--overlay-rgb), 0.1);
  border-radius: 10px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  cursor: pointer;
  font-weight: 500;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease, filter 0.15s ease;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

.modal-actions button:hover {
  background-color: var(--bg-header);
  transform: translateY(-1px);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.35);
}

.modal-actions button:active {
  transform: translateY(0);
}

.event-save,
.category-save {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark)) !important;
  color: white;
  border-color: transparent !important;
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
}

.event-save:hover,
.category-save:hover {
  filter: brightness(1.1);
}

/* ConfirmDialog.js - замена window.confirm() своим стилизованным попапом
   (см. CLAUDE.md). Ширина ограничена явно (а не отдана на откуп содержимому,
   как у остального .modal-box - см. CLAUDE.md "no longer has a fixed
   width: 320px") - вопрос-подтверждение обычно короткий, но перечисление
   нескольких занятых участников/пересекающихся событий в один абзац может
   разрастись, и без ограничения окно растягивалось бы почти на весь экран
   вместо того, чтобы перенести текст на несколько строк. .event-save тут
   переиспользуется как есть (тот же акцентный градиент, что у "Сохранить"),
   отдельная кнопка-иконка/своя раскраска не понадобились. */
.confirm-dialog-box {
  max-width: 380px;
}

.confirm-dialog-message {
  margin: 0 0 16px;
  line-height: 1.5;
  color: var(--text-primary);
}

/* Общий вид disabled-состояния для .event-save (например, пока сохраняется
   личная заметка о пользователе, см. ParticipantPreview.js) - тот же вид,
   что и у .details-delete:disabled. */
.event-save:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  filter: none;
}

.details-delete {
  background: linear-gradient(135deg, var(--color-danger-grad-start), var(--color-danger-grad-end)) !important;
  color: white;
  border-color: transparent !important;
  box-shadow: 0 2px 8px rgba(var(--danger-rgb), 0.5);
}

.details-delete:hover {
  filter: brightness(1.1);
}

.details-skip {
  background-color: rgba(var(--warning-rgb), 0.15) !important;
  color: var(--color-warning);
  border-color: rgba(var(--warning-rgb), 0.4) !important;
}

.details-skip:hover {
  background-color: rgba(var(--warning-rgb), 0.25) !important;
}

.details-time,
.details-place,
.details-priority,
.event-preview-repeat,
.event-preview-visibility,
.event-preview-restriction {
  font-size: 0.9em;
  margin-bottom: 6px;
  color: var(--text-muted);
}

.details-completed {
  font-size: 0.9em;
  margin-bottom: 6px;
  color: var(--color-success);
  font-weight: 600;
}

.details-note {
  font-size: 0.9em;
  color: var(--text-secondary);
  white-space: pre-wrap;
}

/* Базовый вид блока - применяется, когда там оказался обычный текст (адрес
   Места без похожей на ссылку схемы, см. CLAUDE.md "Places"), тот же стиль,
   что и у .details-note. Сама ссылка (.details-external-link-anchor ниже)
   переопределяет цвет/начертание акцентными, раз это призыв к действию, а
   не описательный текст. */
.details-external-link {
  font-size: 0.9em;
  color: var(--text-secondary);
}

.details-external-link-anchor {
  font-size: 0.9em;
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}

.details-external-link-anchor:hover {
  text-decoration: underline;
}

/*****************************
       Event detail page (/schedule/events/<id>/)
******************************/
.event-detail-page {
  max-width: 760px;
  margin: 0 auto;
}

.event-detail-card {
  padding: 0;
  overflow: hidden;
}

.event-detail-hero {
  display: block;
  width: 100%;
  height: 260px;
  object-fit: cover;
}

/* Нет картинки - цветной градиент по цвету категории события вместо пустого
   места (см. --hero-color, проставляется инлайново в event_detail.html). */
.event-detail-hero-placeholder {
  height: 120px;
  background: linear-gradient(135deg, var(--hero-color, var(--accent-solid)), rgba(0, 0, 0, 0.45));
}

.event-detail-hero-wrap {
  position: relative;
}

/* Контейнер для иконок-кнопок поверх обложки события - раньше только одна
   (.event-detail-image-toggle, owner-only), теперь может быть и вторая
   (.event-detail-invite-corner-btn, для публичного события - см.
   event_detail.html) - обе сидят в правом верхнем углу как единый ряд, а не
   каждая сама по себе через свой absolute top/right, иначе при совпадении
   обеих (владелец публичного события) они бы наложились друг на друга. */
.event-detail-hero-actions {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
}

/* Кнопка-иконка загрузки/замены картинки - раньше форма загрузки всегда была
   видна отдельной секцией ниже; теперь свёрнута (.event-detail-section.hidden)
   и открывается по этой кнопке прямо на самой картинке, тем же паттерном,
   что "+" у "Мои группы" (см. GroupsList.js/groups.html). Полупрозрачный
   тёмный фон + blur, а не .details-link-стиль - иконка должна читаться и
   поверх фото, и поверх цветного градиента заглушки. Тот же паттерн
   переиспользован для .event-detail-invite-corner-btn (см. выше) - оба
   класса объединены в один селектор ниже, чтобы не дублировать 34px-круг +
   всегда-тёмный фон дважды. */
.event-detail-hero-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  /* Не через переменные темы - кружок сидит поверх фото/цветной заглушки
     события (см. комментарий выше), а не поверх обычной поверхности
     приложения, так что ему нужен свой всегда-тёмный фон + всегда-белая
     иконка независимо от выбранной темы сайта. */
  background-color: rgba(0, 0, 0, 0.45);
  color: #fff;
  cursor: pointer;
  backdrop-filter: blur(2px);
  transition: background-color 0.15s ease;
}

.event-detail-hero-icon-btn:hover {
  background-color: rgba(var(--accent-solid-rgb), 0.85);
}

.event-detail-hero-icon-btn svg {
  width: 17px;
  height: 17px;
}

/* Панель приглашения, раскрываемая иконкой в углу обложки (см.
   .event-detail-invite-corner-btn выше) - тот же InvitePanel.js виджет, что
   и у owner-only кнопки "Пригласить" в "Управление" ниже, просто в своём
   отдельном месте сразу под обложкой, а не внизу страницы - раскрывается
   рядом с иконкой, которая её открыла, а не где-то далеко от неё. */
#event-detail-hero-invite-panel {
  margin-top: 10px;
}

.event-detail-section.hidden {
  display: none;
}

.event-detail-body {
  padding: 20px 24px 24px;
}

.event-detail-title {
  margin: 0 0 14px;
  font-size: 1.5em;
}

.event-detail-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.event-detail-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 16px;
  background-color: rgba(var(--overlay-rgb), 0.06);
  font-size: 0.85em;
  color: var(--text-muted);
}

.event-detail-pill-completed {
  background-color: rgba(var(--success-rgb), 0.15);
  color: var(--color-success);
}

.event-detail-pill svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  color: var(--text-secondary);
}

/* .category-list-swatch (см. выше) полагается на flex-контейнер родителя,
   чтобы width/height/border-radius вообще применились - тут родитель как раз
   .event-detail-pill (inline-flex), но правило дублируем явно на случай,
   если разметка вокруг когда-нибудь изменится. */
.event-detail-category-swatch {
  display: inline-block;
  vertical-align: middle;
}

.event-detail-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}

.event-detail-tag {
  padding: 3px 10px;
  border-radius: 12px;
  background-color: rgba(var(--accent-rgb), 0.15);
  color: var(--accent);
  font-size: 0.8em;
}

.event-detail-note {
  padding: 12px 14px;
  border-left: 3px solid rgba(var(--overlay-rgb), 0.15);
  background-color: rgba(var(--overlay-rgb), 0.03);
  border-radius: 0 6px 6px 0;
  color: var(--text-muted);
  font-size: 0.9em;
  white-space: pre-wrap;
  margin-bottom: 4px;
}

/* group_detail.html рендерит #group-detail-description-block всегда (не
   только при непустом group.description, как раньше) - GroupDetail.js
   переключает эту же .hidden после сохранения описания без перезагрузки
   страницы, значит серверу нужно отдавать сам элемент в разметке заранее,
   а не создавать его на клиенте с нуля. */
.event-detail-note.hidden {
  display: none;
}

/* Ссылка на внешнюю встречу переиспользует .event-detail-note (левая
   граница + приглушённый фон), но сама ссылка внутри - акцентным цветом, а
   не приглушённым текстом .event-detail-note, раз это призыв к действию,
   не описательный текст. */
.event-detail-external-link a {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}

.event-detail-external-link a:hover {
  text-decoration: underline;
}

.event-detail-section {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid rgba(var(--overlay-rgb), 0.08);
}

.event-detail-section-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 500;
  margin-bottom: 12px;
}

details.event-detail-section summary.event-detail-section-title {
  cursor: pointer;
}

.event-participants-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.event-participant-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Клик по строке - см. ParticipantPreview.js - открывает увеличенную
   аватарку + Имя/Фамилия. Та же лёгкая подсветка при наведении, что и у
   .events-row-clickable на странице событий. .event-comment-row-clickable -
   тот же приём для строки комментария (аватарка + автор, см. CLAUDE.md) -
   кнопка удаления комментария внутри строки не мешает: клик по ней гасится
   тем же "closest('button, a')"-guard'ом в ParticipantPreview.js. */
.event-participant-row-clickable,
.event-comment-row-clickable {
  cursor: pointer;
  border-radius: 6px;
  padding: 4px 6px;
  margin: -4px -6px;
  transition: background-color 0.15s ease;
}

.event-participant-row-clickable:hover,
.event-comment-row-clickable:hover {
  background-color: rgba(var(--overlay-rgb), 0.06);
}

.event-participant-avatar,
.event-participant-avatar-placeholder {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  flex-shrink: 0;
}

.event-participant-avatar {
  object-fit: cover;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
}

.event-participant-avatar-placeholder {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  color: white;
  font-size: 0.85em;
}

/* Комментарии к событию (event_detail.html) - тот же .event-participant-avatar
   для аватарки, что и в списке участников, но align-items: flex-start вместо
   center - в отличие от однострочного имени участника, текст комментария
   может занимать несколько строк. */
.event-comments-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-bottom: 14px;
}

.event-comment-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.event-comment-body {
  flex: 1;
  min-width: 0;
}

.event-comment-header {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.event-comment-author {
  font-weight: 600;
}

.event-comment-time {
  font-size: 0.8em;
  color: var(--text-secondary);
}

.event-comment-text {
  margin: 4px 0 0;
  color: var(--text-muted);
  font-size: 0.9em;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Тише, чем .details-delete (тот зарезервирован для по-настоящему
   необратимых действий вроде удаления всего события) - удаление своего же
   комментария происходит часто и не должно кричать красным гradient'ом на
   всю строку. Прозрачная "ghost"-кнопка, тонирующаяся в красный только на
   hover/focus, тот же 26px-круг, что и .calendar-header-actions .details-link
   на мобильном. */
.event-comment-delete-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: none;
  background-color: transparent;
  color: var(--text-faint);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.event-comment-delete-btn:hover,
.event-comment-delete-btn:focus-visible {
  background-color: rgba(var(--danger-rgb), 0.12);
  color: var(--color-danger);
}

.event-comment-delete-btn svg {
  width: 15px;
  height: 15px;
}

/* Оптимистичный рендер (см. EventDetail.js handleAddComment/CLAUDE.md
   "Comments") - комментарий появляется в списке сразу по отправке, ещё до
   ответа сервера, чуть приглушённый, чтобы отличаться от уже подтверждённых
   строк. Снимается в reconcileCommentRow, как только сервер подтвердит. */
.event-comment-row.event-comment-pending {
  opacity: 0.6;
}

/* Не прошедший на сервер комментарий - тот же левый акцент, что и
   .notification-unread выше, только в тоне ошибки вместо акцентного цвета -
   строка остаётся полностью видимой (не приглушённая, в отличие от
   pending), раз это уже финальное, а не промежуточное состояние. */
.event-comment-row.event-comment-failed {
  border-left: 3px solid var(--color-danger);
  background-color: rgba(var(--danger-rgb), 0.08);
  padding-left: 8px;
}

.event-comment-failed-label {
  font-size: 0.8em;
  color: var(--color-danger);
  cursor: help;
}

.event-comment-form textarea,
.event-decline-form textarea {
  display: block;
  width: 100%;
  min-height: 70px;
  margin-top: 4px;
  padding: 8px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
  resize: vertical;
  box-sizing: border-box;
}

/* Форма отказа от участия (event_detail.html/pending_confirmations.html) -
   причина обязательна, поэтому отдельная форма с textarea, а не единый ряд
   кнопок с "Принять". Изначально скрыта - см. DeclineForm.js: раскрывается
   только после клика по .event-decline-toggle, чтобы textarea не маячила
   на видном месте до того, как человек вообще решил отказаться. */
.event-decline-form {
  margin-top: 10px;
}

.event-decline-form.hidden {
  display: none;
}

.event-decline-toggle.hidden {
  display: none;
}

/* Всплывающая карточка участника по клику на строку - см.
   ParticipantPreview.js. Тот же .modal-overlay/.modal-box, что и у других
   попапов приложения, просто с центрированным содержимым и увеличенной
   аватаркой вместо формы. */
.participant-preview-box {
  position: relative;
  width: 260px;
  text-align: center;
  padding-top: 30px;
}

.participant-preview-close {
  position: absolute;
  top: 10px;
  right: 10px;
}

/* Прямо под крестиком (см. .participant-preview-close выше) - иконка-ссылка
   на анонимизированное расписание этого человека (см. CLAUDE.md "Viewing
   another group-mate's schedule, anonymized"), видна только когда зритель и
   он состоят в общей ACCEPTED-группе (см. data-can-view-schedule/show() в
   ParticipantPreview.js). */
.participant-preview-schedule {
  position: absolute;
  top: 46px;
  right: 10px;
}

.participant-preview-schedule.hidden {
  display: none;
}

/* Ещё на 36px ниже "Посмотреть расписание" - тот же вертикальный шаг
   (30px круг + 6px зазор), что и между ним и крестиком выше. Тот же
   круг доступа (data-can-view-schedule), просто другая ссылка (?compare=
   вместо ?user=, см. CLAUDE.md "Comparing schedules"). */
.participant-preview-compare {
  position: absolute;
  top: 82px;
  right: 10px;
}

.participant-preview-compare.hidden {
  display: none;
}

.participant-preview-avatar-wrap {
  width: 120px;
  height: 120px;
  margin: 0 auto 14px;
  /* Само по себе не обрезает содержимое (аватар/плейсхолдер уже сами
     border-radius: 50%) - нужно только чтобы .is-pro (см. ниже) светился
     кругом, а не квадратом вокруг круглой картинки. */
  border-radius: 50%;
}

.participant-preview-avatar,
.participant-preview-avatar-placeholder {
  width: 120px;
  height: 120px;
  border-radius: 50%;
}

.participant-preview-avatar {
  object-fit: cover;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
}

.participant-preview-avatar-placeholder {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  color: white;
  font-size: 2.4em;
}

.participant-preview-avatar.hidden,
.participant-preview-avatar-placeholder.hidden {
  display: none;
}

.participant-preview-username-row {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 10px;
}

.participant-preview-username {
  color: var(--text-secondary);
  font-size: 0.9em;
}

.participant-preview-field {
  font-size: 0.95em;
  margin-bottom: 4px;
}

/* Цитата/статус (Profile.status_message, см. CLAUDE.md "Личная цитата/
   статус") - курсивом и приглушённым цветом, чтобы читаться как чья-то
   реплика, а не очередное обычное поле вроде имени/фамилии. */
.participant-preview-status-message {
  font-style: italic;
  color: var(--text-faint);
  font-size: 0.9em;
  margin-bottom: 6px;
}

.participant-preview-last-seen {
  color: var(--text-tertiary);
  font-size: 0.8em;
}

/* Карма (см. CLAUDE.md "Карма") - только итоговое число, не полная
   разбивка (та остаётся приватной, видна только на собственной странице
   профиля - см. get_user_note/CLAUDE.md за тем, почему именно так).
   Скрыт по умолчанию (.hidden) - показывается только когда loadNote()
   подтвердит, что зритель либо сам смотрит на себя, либо реальный
   однокурсник по группе. */
.participant-preview-karma {
  color: var(--text-secondary);
  font-size: 0.85em;
  margin-bottom: 6px;
}

/* Личная заметка о собеседнике (см. UserNote/CLAUDE.md "Личные заметки о
   пользователях") - единственный редактируемый блок в этом попапе, поэтому
   ему нужен свой text-align: left поверх центрированного .participant-preview-box,
   иначе label/textarea смотрелись бы криво по центру. Скрыт по умолчанию
   (.hidden) - показывается только когда loadNote() в ParticipantPreview.js
   подтвердит, что заметка доступна (не про самого себя, есть право её видеть).
   Сама textarea/label уже получают базовую вёрстку от общих .modal-box
   textarea/.modal-box label правил выше (этот попап - тоже .modal-box) -
   тут только то, что для них специфично. */
.participant-preview-note {
  text-align: left;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid rgba(var(--overlay-rgb), 0.1);
}

.participant-preview-note-label {
  color: var(--text-secondary);
}

.participant-preview-note-text {
  resize: vertical;
}

.participant-preview-note .modal-actions {
  margin-top: 8px;
}

/* Реальный найденный баг: оба блока выше уже описывали себя как "скрыт по
   умолчанию (.hidden)" в собственных комментариях, но ни один настоящий
   .hidden { display: none } для них так и не был написан - loadNote()
   исправно навешивала/снимала класс, но без этого правила он ничего не
   менял визуально, так что карма и заметка были видны всегда, даже пустыми,
   ещё до того, как loadNote() вообще успевала подтвердить право их
   показывать. Та же самая схема "класс есть, парного правила нет" нашлась
   ещё трижды в EventModal.js - см. .event-group-switch-locked-hint/
   .event-pick-time-hint/.event-remind-me-field выше (тот отчёт был про
   другую подсказку - "Подобрать время", видную даже для личных событий -
   но причина оказалась той же самой). */
.participant-preview-karma.hidden,
.participant-preview-note.hidden {
  display: none;
}

/* "Про"-пользователь (Profile.is_pro, см. CLAUDE.md 'Pro users') - чисто
   визуальное признание вклада в разработку/тестирование, ставится только
   вручную через /admin/. box-shadow (не border!), потому что элементы, к
   которым добавляется этот класс, очень разной формы (строка участника,
   пилюля-чип, круглый аватар навбара, попап предпросмотра) и с разным
   border-radius/box-sizing - inset+обычная тень одинаково следуют за формой
   самого элемента, ничего не сдвигая в раскладке (тот же приём, что и у
   .month-day.today, только с двумя слоями ради эффекта свечения, а не
   просто цветного контура). */
.is-pro {
  box-shadow: inset 0 0 0 1.5px var(--color-warning), 0 0 8px rgba(var(--warning-rgb), 0.5);
}

.pro-badge-star {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-right: 4px;
  color: var(--color-warning);
  flex-shrink: 0;
  filter: drop-shadow(0 0 2px rgba(var(--warning-rgb), 0.6));
  vertical-align: -2px;
}

.pro-badge-star svg {
  width: 100%;
  height: 100%;
}

/* "Бета-тестер" (сейчас - User.is_staff, см. CLAUDE.md 'Бета-тестеры') -
   тот же приём/геометрия, что и у .is-pro/.pro-badge-star выше, просто
   акцентным (--accent), а не warning-цветом, чтобы два статуса читались
   по-разному с одного взгляда. Бета показывается ВМЕСТО про, если у
   пользователя оба статуса разом (см. BetaBadge.js resolveBadgeKind) - тут
   намеренно нет правила ".is-pro.is-beta", раз оба класса на одном элементе
   никогда не проставляются одновременно. */
.is-beta {
  box-shadow: inset 0 0 0 1.5px var(--accent), 0 0 8px rgba(var(--accent-rgb), 0.5);
}

.beta-badge-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-right: 4px;
  color: var(--accent);
  flex-shrink: 0;
  filter: drop-shadow(0 0 2px rgba(var(--accent-rgb), 0.6));
  vertical-align: -2px;
}

.beta-badge-icon svg {
  width: 100%;
  height: 100%;
}

/* Тег с непустым description - см. TagPreview.js. Курсор-указатель отмечает,
   что чип кликабелен (обычный тег без описания остаётся немым текстом). */
.tag-preview-trigger {
  cursor: pointer;
}

/* Попап с полным текстом описания тега по клику - тот же .modal-overlay/
   .modal-box, что и .participant-preview-box выше, просто с цветным свотчем
   вместо аватарки. */
.tag-preview-box {
  position: relative;
  width: 280px;
  padding-top: 30px;
}

.tag-preview-close {
  position: absolute;
  top: 10px;
  right: 10px;
}

.tag-preview-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.tag-preview-swatch {
  width: 16px;
  height: 16px;
  border-radius: 4px;
  flex-shrink: 0;
}

.tag-preview-name {
  font-weight: bold;
  font-size: 1.1em;
}

.tag-preview-description {
  color: var(--text-muted);
  font-size: 0.9em;
  white-space: pre-wrap;
  margin: 0;
}

/* Готовые "сборники тегов" (TagBundles.js) в TagModal.js - показываются
   только когда onApplyBundle подключён (только GroupDetail.js, см. CLAUDE.md
   'Tags'). */
.tag-bundles.hidden {
  display: none;
}

.tag-bundles {
  margin-bottom: 14px;
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(var(--overlay-rgb), 0.1);
}

.tag-bundles-label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.9em;
  color: var(--text-secondary);
}

.tag-bundles-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Кроппер изображений (ImageCropper.js) - рамка с overflow:hidden, картинка
   внутри позиционируется через transform: translate(), которым JS двигает
   её при перетаскивании/зуме. Раньше рамка была всегда квадратной/круглой
   (только аватарка) - ширина/высота больше не фиксированы здесь вовсе,
   ImageCropper.js выставляет их инлайново под каждый конкретный options
   (аватар/обложка события-группы-идеи, см. его же комментарий) - CSS тут
   отвечает только за форму по умолчанию (круг) и общий внешний вид рамки. */
.cropper-box {
  width: 300px;
  text-align: center;
  /* .modal-box's base rule sets overflow-y: auto with no explicit overflow-x -
     per the CSS Overflow spec that makes the UA compute overflow-x: auto too,
     so any sub-pixel horizontal overflow (circular frame, range slider) was
     showing a stray horizontal scrollbar along the bottom of the dialog -
     same root cause .month-day-events already hit/documented once before. */
  overflow-x: hidden;
}

.cropper-frame {
  position: relative;
  margin: 0 auto 14px;
  overflow: hidden;
  border-radius: 50%;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  background-color: var(--bg-inset);
  cursor: grab;
  touch-action: none;
}

/* Прямоугольная рамка (обложки события/группы/идеи, см. FileInput.js'ов
   CROP_PRESETS.hero) - тот же радиус, что и у .modal-box/карточек этого
   приложения (8px), а не круг, унаследованный по умолчанию от .cropper-frame
   выше. Два класса вместо одного - специфичность выше базового правила
   независимо от порядка объявления в файле. */
.cropper-frame.cropper-frame-rect {
  border-radius: 8px;
}

.cropper-frame:active {
  cursor: grabbing;
}

.cropper-image {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: top left;
  user-select: none;
  -webkit-user-drag: none;
}

.cropper-image.hidden {
  display: none;
}

.cropper-zoom-slider {
  width: 100%;
}

.event-participant-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.event-participant-name {
  font-size: 0.9em;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.event-participant-meta {
  font-size: 0.78em;
  color: var(--text-secondary);
}

.event-participant-status-pending {
  color: var(--color-warning);
}

.event-participant-status-declined {
  color: var(--text-danger);
}

.event-participant-attendance-attended {
  color: var(--color-success);
}

.event-participant-attendance-no_show {
  color: var(--text-danger);
}

.event-participant-attendance-actions {
  margin-left: auto;
  flex-wrap: nowrap;
}

/* "Активные события группы" на group_detail.html (см. CLAUDE.md) - явно
   ужатый формат по запросу: тот же размер строки, что и у
   .event-participant-row выше (32px-кружок + gap:10px + название/подпись
   в столбик), а не полноразмерная .events-row со страницы "Событий"
   (там своя цветная плашка-акцент, несколько рядов чипов дата/место/
   организатор/теги/участники, панель приглашения - на этой странице всё
   это было бы избыточно, тут только "что происходит и куда кликнуть").
   Простая серверная <a>, а не JS-обработчик клика по строке - тут нет
   вложенных интерактивных элементов (в отличие от .events-row-clickable
   на странице "Событий"), которые нужно было бы гасить через closest(). */
.group-events-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.group-event-row {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  border-radius: 6px;
  padding: 4px 6px;
  margin: -4px -6px;
  transition: background-color 0.15s ease;
}

.group-event-row:hover {
  background-color: rgba(var(--overlay-rgb), 0.06);
}

.group-event-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  flex-shrink: 0;
  color: white;
}

.group-event-icon svg {
  width: 16px;
  height: 16px;
}

.group-event-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.group-event-title {
  font-size: 0.9em;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.group-event-meta {
  font-size: 0.78em;
  color: var(--text-secondary);
}

/* Лента активности группы (GroupActivity, см. CLAUDE.md "Group detail
   page"/models.py) - плоский список "сообщение + когда", без аватарок и
   иконок на строку - сознательно проще, чем .event-participant-row/
   .group-event-row: это лёгкий журнал, а не список с кликабельными
   карточками. */
.group-activity-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.group-activity-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(var(--overlay-rgb), 0.06);
}

.group-activity-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.group-activity-message {
  font-size: 0.9em;
}

.group-activity-time {
  flex-shrink: 0;
  font-size: 0.75em;
  color: var(--text-faint);
  white-space: nowrap;
}

/* "Особая пометка" модератора группы (см. CLAUDE.md 'Groups') - маленький
   бейдж рядом с именем в списке участников group_detail.html. */
.group-moderator-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  color: white;
  font-size: 0.72em;
  font-weight: 600;
  vertical-align: middle;
}

/* Раньше - две подчёркнутые текстовые ссылки одна под другой ("Посмотреть
   расписание"/"Сравнить расписание"), сейчас - два маленьких кружка-иконки
   в ряд (тот же .details-link, что и везде в приложении, просто уменьшенный
   под плотность строки участника - явный ask по итогам разбора UX/UI
   ("слишком много текста в строке участника"). group-member-action-link
   больше не задаёт свой собственный вид - .details-link уже даёт кружок с
   иконкой, этот класс только сужает его с 30px до 26px под контекст. */
.group-member-schedule-links {
  display: flex;
  gap: 4px;
  margin-top: 4px;
}

.group-member-action-link.details-link {
  width: 26px;
  height: 26px;
}

.group-member-action-link.details-link svg {
  width: 14px;
  height: 14px;
}

.group-member-actions {
  margin-left: auto;
  flex-wrap: nowrap;
}

/* Явные парные .hidden-правила (в этом кодбейзе нет общего .hidden { display: none }
   - см. CLAUDE.md "Push-уведомления") - все три теперь рендерятся сервером
   всегда и переключаются через GroupDetail.js после ответа на приглашение/
   заявку, а не то присутствуют, то отсутствуют в разметке (см.
   group_detail.html/handleRespondToGroupInvite/handleRespondToJoinRequest). */
.event-participant-meta.hidden,
.group-member-schedule-links.hidden,
.group-member-actions.hidden {
  display: none;
}

/* Вкладки страницы группы (Участники/Обзор/Теги и места/Управление, см.
   CLAUDE.md "Groups"/group_detail.html) - явный ask по итогам разбора UX/UI
   ("одна длинная колонка аккордеонов"). Тот же вид, что и у
   .events-time-toggle (EventsList.js "Предстоящие"/"Прошедшие") -
   переиспользуется через общий селектор, а не копией правил, раз это один и
   тот же визуальный язык "переключатель режимов", просто под другим именем
   класса на этой странице. */
.group-detail-tabs,
.events-time-toggle {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.group-detail-tab-btn,
.events-time-toggle-btn {
  padding: 6px 14px;
  border: none;
  border-radius: 16px;
  background-color: rgba(var(--overlay-rgb), 0.08);
  color: var(--text-primary);
  cursor: pointer;
  font-size: 0.9em;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.group-detail-tab-btn.active,
.events-time-toggle-btn.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
}

.group-detail-tab-panel.hidden {
  display: none;
}

/* Поиск/сортировка списка участников (явный ask по итогам разбора UX/UI) -
   .search-input уже стилизован (см. SearchModal.js), переиспользуется как
   есть; select рядом получает ту же высоту/скругление через общий селектор
   ниже, а не копию тех же семи свойств. */
.group-member-controls {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.group-member-controls .search-input {
  flex: 1;
  min-width: 160px;
  margin: 0;
}

.group-member-sort-select {
  padding: 8px 10px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
}

/* Участник, не подходящий под текущий поисковый запрос (GroupDetail.js'
   filterMembers) - скрыт, а не убран из DOM, чтобы сортировка/фильтр не
   мешали друг другу (сортировка просто переставляет узлы, фильтр только
   красит их display). */
.event-participant-row.group-member-row-filtered {
  display: none;
}

/* Разделитель "Остальные участники" (GroupDetail.js' sortMembers, режим
   "Сначала владелец и модераторы") - вставляется только когда в списке
   реально есть оба кластера, показывает, где заканчивается верхний
   ("владелец и модераторы") и начинается обычный список. */
.group-member-sort-divider {
  padding: 8px 4px 4px;
  font-size: 0.78em;
  color: var(--text-faint);
  border-top: 1px solid rgba(var(--overlay-rgb), 0.08);
  margin-top: 6px;
}

/* Увеличение обложки группы по клику (GroupDetail.js' openHeroLightbox) -
   явный ask по итогам разбора UX/UI. .modal-overlay уже центрирует
   содержимое флексом, тут нужен только сам размер картинки внутри. */
.group-detail-hero-clickable {
  cursor: zoom-in;
}

.group-detail-lightbox-image {
  max-width: 90vw;
  max-height: 90vh;
  border-radius: 8px;
  object-fit: contain;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.icon-btn svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/*****************************
       Month view
******************************/
.month-layout {
  position: relative;
  padding: 15px;
  max-width: 1200px;
  margin: 0 auto;
}

.month-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  margin-bottom: 15px;
}

.month-title {
  font-size: 1.3em;
  font-weight: bold;
  min-width: 180px;
  text-align: center;
}

.month-weekdays {
  display: grid;
  grid-template-columns: 28px repeat(7, 1fr);
  font-weight: bold;
  text-align: center;
  margin-bottom: 5px;
  color: var(--text-secondary);
}

.month-grid {
  display: grid;
  grid-template-columns: 28px repeat(7, 1fr);
  grid-auto-rows: minmax(130px, auto);
  gap: 6px;
}

.month-week-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 1.1em;
  border-radius: 4px;
  background-color: rgba(var(--overlay-rgb), 0.04);
  transition: background-color 0.15s ease, color 0.15s ease;
}

.month-week-nav:hover {
  background-color: rgba(var(--accent-solid-rgb), 0.25);
  color: var(--text-primary);
}

.month-day {
  min-width: 0; /* иначе длинный текст события может растянуть колонку грида */
  border: 1px solid rgba(var(--overlay-rgb), 0.08);
  border-radius: 4px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  background-color: var(--bg-surface-2);
}

.month-day.other-month {
  opacity: 0.4;
}

.month-day.today {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.month-day.today .month-day-number {
  color: var(--accent);
}

.month-day-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.month-day-number {
  font-weight: bold;
}

.month-day .add-event-btn {
  background-color: rgba(var(--overlay-rgb), 0.1);
  color: var(--text-primary);
}

.month-day .add-event-btn:hover {
  background-color: var(--accent-solid);
}

.month-day-events {
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-height: 110px;
  overflow-y: auto;
  /* Без явного overflow-x браузер трактует его как auto (раз overflow-y не visible),
     и любой даже однопиксельный сдвиг чипа при hover (translateX) вызывал
     горизонтальный скроллбар. */
  overflow-x: hidden;
  /* Тонкий скроллбар в цвет темы вместо жирного нативного (Firefox). */
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--overlay-rgb), 0.35) transparent;
}

/* То же самое для Chrome/Safari/Edge - у них своя, отдельная от
   scrollbar-width/-color система стилизации. Тот же набор правил для
   .modal-box - см. комментарий у .modal-box выше. */
.month-day-events::-webkit-scrollbar,
.modal-box::-webkit-scrollbar {
  width: 4px;
}

.month-day-events::-webkit-scrollbar-track,
.modal-box::-webkit-scrollbar-track {
  background: transparent;
}

.month-day-events::-webkit-scrollbar-thumb,
.modal-box::-webkit-scrollbar-thumb {
  background-color: rgba(var(--overlay-rgb), 0.35);
  border-radius: 4px;
}

.month-day-events::-webkit-scrollbar-thumb:hover,
.modal-box::-webkit-scrollbar-thumb:hover {
  background-color: rgba(var(--overlay-rgb), 0.55);
}

/* Дни рождения участников групп (см. MonthView.js's buildDayCell/
   _group_birthdays_in_range в views/events.py) - отдельная строка над
   обычными чипами событий, не переиспользует .event-chip: это не Event
   (нет своего --event-color/категории), а просто именной значок. Розовый
   акцент - намеренно фиксированный, не через var(--accent)/--overlay-rgb
   (тот же прецедент, что и .events-invite-qr/.tooltip - см. CLAUDE.md
   "Тема оформления"): смысл значка ("сегодня чей-то день рождения") не
   должен теряться/сливаться с фоном при смене темы, ему не нужно
   согласовываться с акцентным цветом остального интерфейса. */
.month-day-birthdays {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 3px;
}

.month-day-birthday-badge {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.72em;
  padding: 2px 5px;
  border-radius: 6px;
  background-color: rgba(233, 30, 140, 0.16);
  color: var(--text-primary);
}

.month-day-birthday-badge svg {
  width: 11px;
  height: 11px;
  flex-shrink: 0;
  color: #e91e8c;
}

.month-day-birthday-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.event-chip {
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-size: 0.75em;
  padding: 3px 6px;
  border-radius: 6px;
  border-left: 4px solid var(--event-color);
  background-color: rgba(var(--overlay-rgb), 0.06);
  color: var(--text-primary);
  cursor: pointer;
  transition: transform 0.15s ease, background-color 0.15s ease;
}

.event-chip:hover {
  background-color: rgba(var(--overlay-rgb), 0.16);
  transform: translateX(2px);
}

.event-chip-time {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex-shrink: 0;
  opacity: 0.85;
}

/* Значки "повторяется"/"групповое" (см. EventBadges.js) - общие для чипа
   месяца (внутри .event-chip-time) и подписи недели (внутри .event-label);
   намеренно мелкие и приглушённые (opacity), чтобы не спорить за внимание
   с самим временем/названием на и так тесных элементах. */
.event-badge-icon {
  display: inline-flex;
  flex-shrink: 0;
  opacity: 0.75;
}

.event-badge-icon svg {
  width: 10px;
  height: 10px;
  display: block;
}

/* Десктоп: время + название в одну строку, название обрезается многоточием
   при переполнении (min-width:0 обязателен - иначе flex-элемент не сжимается
   меньше своего контента, и text-overflow никогда не сработает). */
.event-chip-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*****************************
       Tag checklist (в форме шаблона события - см. TemplateModal.js;
       EventModal.js использует select+чипы, см. ниже)
******************************/
.tag-checkbox {
  display: flex !important;
  align-items: center;
  gap: 4px;
  margin-bottom: 0 !important;
  font-weight: normal !important;
  width: auto !important;
}

.tag-checkbox input {
  width: auto !important;
  margin: 0 !important;
}

/* Теги события (EventModal.js) - select "+ добавить тег" выше + чипы уже
   выбранных ниже, тот же визуальный паттерн, что и у участников по
   значимости на странице планирования (.plan-tier-member/-remove/-empty). */
.event-tags-selected {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 4px 0 12px;
}

.event-tags-empty {
  color: var(--text-secondary);
  font-size: 0.8em;
}

.event-tag-chip {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 4px 3px 10px;
  border-radius: 12px;
  background-color: rgba(var(--accent-solid-rgb), 0.2);
  font-size: 0.85em;
  white-space: nowrap;
}

.event-tag-remove {
  border: none;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 1em;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 50%;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.event-tag-remove:hover {
  background-color: rgba(var(--danger-rgb), 0.25);
  color: var(--text-danger);
}

/* Мини-форма "+ Создать новый тег" (EventModal.js, submitTagCreate) -
   раскрывается прямо под select'ом добавления тега, тот же build-once/
   toggle-hidden приём, что и у остальных сворачиваемых секций формы события
   (.event-category-row/.event-place-row и т.п.) - явное .hidden-правило
   обязательно (в этом кодбейзе нет общего .hidden { display: none } - см.
   CLAUDE.md "Push-уведомления"). */
.event-tag-create.hidden {
  display: none;
}

.event-tag-create {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
}

.event-tag-create-name {
  flex: 1;
  min-width: 0;
}

.modal-participants {
  margin-bottom: 12px;
  font-size: 0.9em;
  color: var(--text-secondary);
}

.details-owner,
.details-participants {
  font-size: 0.9em;
  margin-bottom: 6px;
  color: var(--text-muted);
}

/*****************************
       Profile page
******************************/
/* profile.html переиспользует .plan-form для каждой из своих карточек
   (Мой профиль/Telegram/пароль/категории-шаблоны-места/подписка на
   календарь/теги/статистика/посещаемость/документы - см. CLAUDE.md), но им
   нужен СВОЙ контейнер, а не .plan-layout ниже: та - обычный flex-ряд без
   переноса, рассчитанный на 1-2 дочерних элемента (форма+результаты на
   /plan/, один .plan-form на страницах приглашений) - при 9 карточках
   подряд, каждая с фиксированной шириной 320px (.plan-form ниже), это
   вытягивалось в один нескончаемый ряд вправо за пределы экрана вместо того,
   чтобы переноситься на новую строку.

   Сначала пробовали CSS Grid - но у карточек очень разная высота (пароль -
   три строки, статистика - таблица), а Grid выравнивает по СТРОКАМ (высота
   строки = высота самой высокой карточки в ней), оставляя пустоту под
   короткими соседями вместо того, чтобы следующая карточка "поднялась" и
   заняла это место. Ровно то, для чего масонри-раскладки и существуют -
   CSS многоколоночная вёрстка (columns) укладывает карточки одну под другой
   ВНУТРИ колонки (не по строкам поперёк всех колонок), поэтому короткие
   карточки не оставляют дыр под собой. Порядок чтения при этом становится
   "сверху вниз по первой колонке, потом по второй" вместо привычного
   слева-направо построчного - на странице профиля это нормально: карточки
   независимы друг от друга, не пошаговый мастер, где порядок важен.
   break-inside: avoid на самих карточках (см. ниже) - чтобы длинная
   карточка не разрезалась пополам на границе колонок. */
.profile-grid {
  columns: 320px;
  column-gap: 20px;
  max-width: 1200px;
  margin: 0 auto;
}

.profile-grid .plan-form {
  /* Точная ширина колонки - не обязательно ровно 320px (браузер сам решает,
     сколько колонок влезает и как поделить между ними ширину контейнера) -
     100% вместо фиксированного .plan-form { width: 320px } ниже, чтобы
     карточка всегда точно заполняла свою колонку, а не спорила с ней за
     ширину. */
  width: 100%;
  break-inside: avoid;
  margin-bottom: 20px;
}

/* Группировка нескольких родственных карточек ("Уведомления" - push +
   Telegram, "Синхронизация календаря" - webcal-подписка + ICS-по-ссылке) в
   один сворачиваемый блок вместо нескольких отдельных карточек подряд -
   явный запрос ("давай сгруппируем ... в сворачиваемые блоки"), тот же
   нативный <details>, что и .event-detail-section (event_detail.html/
   group_detail.html), просто без открытия по умолчанию - на профиле это
   вспомогательные настройки, которые нет смысла всегда показывать
   развёрнутыми. .plan-form остаётся на самом <details> (не на вложенном
   <div>), чтобы карточка по-прежнему была одним элементом масонри-колонки
   (.profile-grid .plan-form выше) - сворачивание/разворачивание работает
   через нативный <details>, без единой строчки JS. */
.profile-group-summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: 1.1em;
  font-weight: bold;
}

.profile-group-summary::-webkit-details-marker,
.profile-group-summary::marker {
  display: none;
}

/* Тот же приём, что и у .events-participants-chevron (events.html) - своя
   иконка-шеврон вместо нативного треугольника-маркера браузера. */
.profile-group-chevron {
  display: inline-flex;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}

.profile-group-chevron svg {
  width: 14px;
  height: 14px;
}

details.profile-group[open] .profile-group-chevron {
  transform: rotate(90deg);
}

/* Каждая карточка внутри сгруппированного блока (Push/Telegram,
   Подписка/Синхронизация) отделена тонкой границей сверху - тот же приём,
   что и .event-detail-section, но не сама вложенно-сворачиваемая, только
   визуально разделённая, раз двойная вложенность <details> внутри <details>
   для этих небольших подсекций была бы лишней сложностью. */
.profile-group-subsection {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid rgba(var(--overlay-rgb), 0.08);
}

/* Сразу после <summary> - без верхней границы/отступа, т.к. это первая
   подсекция блока (селектор по соседству с summary, а не :first-of-type -
   тот сравнивает только по тегу, не по классу, и мог бы случайно
   совпасть не с тем div'ом, появись перед первой подсекцией что-то ещё). */
.profile-group-summary + .profile-group-subsection {
  margin-top: 12px;
  padding-top: 0;
  border-top: none;
}

.profile-group-subsection h3 {
  margin: 0 0 6px;
  font-size: 1em;
}

/* Якорная мини-навигация профиля (явный ask по итогам разбора UX/UI -
   "нет способа быстро перейти к разделу") - тот же вид "пилюль", что и
   .events-time-toggle-btn, просто как ссылки, а не переключатель режимов.
   position: sticky работает относительно .container (тот и есть реальный
   скроллящийся элемент этой страницы, см. его собственный overflow:auto в
   styles.css) - не относительно viewport, но эффект тот же: планка
   остаётся на виду, пока страница крутится под ней. */
.profile-jumpnav {
  position: sticky;
  top: 8px;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-width: 1200px;
  margin: 0 auto 18px;
  padding: 8px;
  background-color: var(--bg-surface);
  border: 1px solid rgba(var(--overlay-rgb), 0.08);
  border-radius: 999px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

.profile-jumpnav a {
  padding: 6px 12px;
  border-radius: 999px;
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.82em;
  font-weight: 600;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.profile-jumpnav a:hover,
.profile-jumpnav a:focus-visible {
  background-color: rgba(var(--accent-rgb), 0.12);
  color: var(--accent);
}

/* Переход по якорям внутри страницы (мини-навигация выше, перекрёстная
   ссылка Telegram->Уведомления ниже) - плавная прокрутка вместо мгновенного
   прыжка. prefers-reduced-motion уважается тем же способом, каким уже
   пользуется NavMenu.js для свайпа шторки "Ещё". */
html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/* scroll-margin-top - чтобы цель якоря не пряталась точно под липкой
   мини-навигацией после прокрутки. */
.profile-group {
  scroll-margin-top: 60px;
}

.profile-group-subsection {
  scroll-margin-top: 60px;
}

/* Перекрёстная ссылка Telegram-карточки на подраздел "Уведомления в
   Telegram" (явный ask - "две Telegram-карточки были разнесены по странице
   без какой-либо связи"). */
.profile-crosslink {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 10px;
  padding: 8px 10px;
  border-radius: 8px;
  background-color: rgba(var(--accent-rgb), 0.09);
  color: var(--accent);
  text-decoration: none;
  font-size: 0.85em;
  font-weight: 600;
}

.profile-crosslink:hover {
  background-color: rgba(var(--accent-rgb), 0.16);
}

.profile-crosslink svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

/* Статус-пилюля "аккаунт привязан" рядом с заголовком подраздела
   "Уведомления в Telegram" - обратная сторона той же перекрёстной ссылки
   выше: тут сразу видно состояние привязки, не поднимаясь к карточке
   Telegram, чтобы проверить. */
.profile-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.62em;
  font-weight: 700;
  text-transform: none;
  vertical-align: middle;
  margin-left: 4px;
}

.profile-status-pill-ok {
  background-color: rgba(var(--success-rgb), 0.15);
  color: var(--color-success);
}

/* Живой счётчик символов пароля (явный ask - "непонятно, сколько уже
   введено до порога в 150") - только визуальная подсказка, set_account_password
   сам по-прежнему проверяет длину на сервере независимо от этого. */
.profile-pw-counter {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: -6px 0 12px;
  font-size: 0.78em;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.profile-pw-bar {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background-color: var(--bg-inset);
  overflow: hidden;
}

.profile-pw-bar-fill {
  display: block;
  height: 100%;
  width: 0%;
  background-color: var(--color-danger);
  transition: width 0.15s ease, background-color 0.15s ease;
}

.profile-pw-bar-fill.profile-pw-ready {
  background-color: var(--color-success);
}

.profile-pw-counter .profile-pw-count-ready {
  color: var(--color-success);
}

/* "Опасная зона" (явный ask - "удаление аккаунта выглядит как обычная
   настройка"): красный оттенок на всей группе целиком, не только на кнопке
   внутри неё - та же rgba(var(--danger-rgb), ...)-тонировка, что и у
   .flash-message.flash-error, только сильно приглушённая (0.04/0.03), раз
   тут читать обычный текст поверх фона нужно весь день, а не одну строку
   несколько секунд. */
.profile-group-danger {
  border-color: rgba(var(--danger-rgb), 0.35);
  background-color: rgba(var(--danger-rgb), 0.04);
}

.profile-group-danger .profile-group-summary,
.profile-group-danger .profile-danger-icon {
  color: var(--color-danger);
}

/*****************************
       Event planner page
******************************/
.plan-layout {
  display: flex;
  align-items: flex-start;
  gap: 20px;
}

.plan-form,
.plan-results {
  background-color: var(--bg-surface);
  border: 1px solid rgba(var(--overlay-rgb), 0.08);
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  color: var(--text-primary);
  padding: 20px;
}

.plan-form {
  width: 320px;
  flex-shrink: 0;
}

.plan-results {
  flex-grow: 1;
  min-width: 0;
}

.plan-form h2,
.plan-results h2 {
  margin-top: 0;
  font-size: 1.1em;
}

.plan-manual-slot {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid rgba(var(--overlay-rgb), 0.1);
}

.plan-manual-slot h3 {
  margin: 0 0 6px;
  font-size: 1em;
}

.plan-form label,
.events-card label {
  display: block;
  margin-bottom: 12px;
  font-size: 0.9em;
  font-weight: 500;
}

.plan-form input,
.plan-form select,
.events-card input[type="text"] {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 6px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
  box-sizing: border-box;
}

/* Чекбокс "Игнорировать мою занятость" - .plan-form label/input выше
   рассчитаны на текстовые поля (label - блок с текстом сверху, input -
   растянутый на всю ширину), что для чекбокса выглядело бы как гигантская
   кликабельная область без самого квадратика рядом с текстом. Более
   специфичные селекторы ниже перебивают оба правила именно для этого поля. */
.plan-form label.plan-checkbox-label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.plan-form input[type="checkbox"] {
  display: inline-block;
  width: auto;
  margin: 0;
}

/* .plan-form input выше (specificity 0,1,1 - класс + тип) перебивает
   собственное правило .file-input-native (0,1,0 - только класс, см. ниже
   "Styled file input"), которое держит инпут визуально скрытым, но
   доступным по Tab (position:absolute; width:1px; clip:rect(0,0,0,0) и
   т.п. - стандартный accessible-hidden приём, НЕ display:none). Внутри
   .plan-form (аватарка на странице профиля - единственное место, где
   .file-input-native оказывается именно в .plan-form, не в .event-detail-*)
   он вместо этого растягивался на всю ширину карточки с ненулевым left-
   отступом, из-за чего вся страница на мобильном получала лишние ~30-40px
   прокрутки вправо. Тот же "два класса перебивают класс+тип" приём, что и
   у .plan-form .events-invite-link ниже. */
.plan-form .file-input-native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Виджет ссылки-подписки на календарь (profile.html, переиспользует
   InvitePanel.js целиком, как и приглашения в событие/группу) - тот же
   конфликт, что и у чекбокса выше: .plan-form input задаёт
   width:100%/margin-top:4px каждому обычному <input>, а .events-invite-link
   рассчитан на flex:1 внутри .events-invite-link-row без верхнего отступа.
   Два класса вместо одного (0,2,0) перебивают .plan-form input (0,1,1) по
   специфичности. */
.plan-form .events-invite-link {
  width: auto;
  margin-top: 0;
}

.plan-user-list {
  max-height: 360px;
  overflow-y: auto;
  margin-bottom: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.plan-tier {
  border: 1px solid rgba(var(--overlay-rgb), 0.08);
  border-radius: 6px;
  padding: 8px 10px;
  background-color: rgba(var(--overlay-rgb), 0.03);
}

.plan-tier-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}

.plan-tier-title {
  font-weight: 500;
  font-size: 0.9em;
}

.plan-tier-add {
  flex-shrink: 0;
  max-width: 55%;
  padding: 4px 6px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font-size: 0.8em;
}

.plan-tier-members {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.plan-tier-empty {
  color: var(--text-secondary);
  font-size: 0.8em;
}

.plan-tier-member {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 4px 3px 10px;
  border-radius: 12px;
  background-color: rgba(var(--accent-solid-rgb), 0.2);
  font-size: 0.85em;
  white-space: nowrap;
}

.plan-tier-remove {
  border: none;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 1em;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 50%;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.plan-tier-remove:hover {
  background-color: rgba(var(--danger-rgb), 0.25);
  color: var(--text-danger);
}

.plan-search-btn {
  width: 100%;
}

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

.plan-result-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border-radius: 6px;
  background-color: rgba(var(--overlay-rgb), 0.04);
}

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

.plan-result-score {
  font-weight: bold;
  color: var(--accent);
  white-space: nowrap;
}

/* "Моя карма" (см. profile.html/karma.py) - крупное число сверху, разбор по
   сигналам списком под ним. */
.karma-score {
  margin: 4px 0 12px;
  font-size: 2.4em;
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
}

.karma-score-max {
  font-size: 0.4em;
  font-weight: 500;
  color: var(--text-secondary);
}

.karma-breakdown {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.karma-breakdown-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 0.85em;
  color: var(--text-secondary);
}

.karma-breakdown-label {
  flex: 1;
}

.karma-breakdown-delta {
  font-weight: 600;
  white-space: nowrap;
}

.karma-breakdown-positive .karma-breakdown-delta {
  color: var(--color-success);
}

.karma-breakdown-negative .karma-breakdown-delta {
  color: var(--color-danger);
}

.plan-empty {
  color: var(--text-secondary);
  font-size: 0.9em;
  padding: 8px 0;
}

/* Явная пара для #event-detail-notify-group-message (см. event_detail.html/
   "Оповещение группы о событии") - тот же принцип, что и у .push-unsupported.hidden
   и соседей чуть ниже: .plan-empty сама по себе не даёт .hidden скрывать. */
#event-detail-notify-group-message.hidden {
  display: none;
}

/* Тот же самый баг, найденный по жалобе: "Пока ничего..." на странице
   "Требующие подтверждения" (см. PendingConfirmations.js's updateEmptyState/
   pending_confirmations.html) показывалась всегда, даже когда приглашения
   были - у #pending-empty-message ни разу не было своей .hidden-пары, так
   что и серверный class="plan-empty hidden" при непустом списке, и
   последующие classList.toggle('hidden', ...) из JS ничего не скрывали
   фактически. */
#pending-empty-message.hidden {
  display: none;
}

/* .hidden сам по себе нигде не определён как общий display:none - у каждого
   скрываемого элемента в этом приложении своё собственное парное правило
   (см. например .modal-overlay.hidden) - эти четыре состояния карточки
   "Push-уведомления в браузере" (profile-index.js's initPushNotifications)
   такого правила не имели вовсе, так что classList.toggle('hidden') на них
   никогда фактически ничего не скрывал - все четыре рендерились разом
   одно поверх другого. */
.push-unsupported.hidden,
.push-denied.hidden,
.push-enable-row.hidden,
.push-enabled-row.hidden {
  display: none;
}

/* Та же история для карточки "Установка приложения" (см.
   profile-index.js's initPwaInstall) - сразу с парным правилом, чтобы не
   повторить баг выше. */
.pwa-install-already.hidden,
.pwa-install-row.hidden,
.pwa-install-manual.hidden {
  display: none;
}

/* Ссылки внутри пустых состояний (EventsList.js - "Подобрать время для
   встречи", EventPlanner.js - "«Группы»") без этого правила рендерились
   стандартным браузерным синим - ярко выбивалось на фоне остального
   приглушённо-серого .plan-empty текста. Чуть светлее самого текста (не
   белый - остаётся в том же приглушённом регистре), подчёркивание оставлено
   (это единственный визуальный сигнал "это ссылка" у простого текста внутри
   абзаца, в отличие от кнопок-ссылок вроде .details-link, где подчёркивание
   сознательно убрано в других местах приложения). */
.plan-empty a {
  color: var(--text-link-muted);
  text-decoration: underline;
}

.plan-empty a:hover {
  color: var(--text-primary);
}

.plan-empty a svg {
  width: 0.85em;
  height: 0.85em;
  vertical-align: -0.08em;
  margin-left: 2px;
}

/* Календарь-heatmap доступности (EventPlanner.renderGrid) - заменяет
   прежний текстовый список найденных окон: каждый получасовой слот - своя
   кликабельная ячейка, подсвеченная по score/max_score (см. views.py
   find_availability), а не готовое "окно" одной строкой. */
.availability-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 12px;
  font-size: 0.85em;
  color: var(--text-primary);
}

.availability-legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
}

.availability-legend-swatch {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border: 1px solid rgba(var(--overlay-rgb), 0.25);
}

/* Тот же тёмно-бордовый цвет и штриховка, что и у самих недоступных ячеек
   ниже - образец в легенде должен выглядеть идентично настоящей ячейке. */
.availability-legend-swatch-blocked {
  background-color: var(--bg-danger-subtle);
  background-image: repeating-linear-gradient(
    135deg, rgba(var(--overlay-rgb), 0.06) 0, rgba(var(--overlay-rgb), 0.06) 2px,
    transparent 2px, transparent 6px
  );
}

/* Таблица может быть шире экрана при широком диапазоне дат - в отличие от
   основного недельного/месячного календаря (где горизонтальный скролл был
   осознанно отвергнут, см. CLAUDE.md "Mobile/responsive"), это специальный
   инструмент подбора времени, а не основной вид календаря: диапазон дат тут
   выбирает сам пользователь, и скролл в этом контексте ожидаем и приемлем. */
.availability-grid-wrap {
  overflow-x: auto;
}

/* table-layout:fixed + явная ширина первой колонки (а НЕ width:100% на
   самой таблице) - раньше таблица тянулась на всю ширину .plan-results, но
   единственная колонка без заданной ширины (подписи времени) забирала всё
   освободившееся пространство целиком (авто-раскладка распределяет разницу
   между "резиновыми" колонками, а резиновой тут была только эта одна) -
   визуально это читалось как широкий пустой отступ слева, будто сама сетка
   начинается не от края, а откуда-то из середины карточки. Без width:100%
   таблица теперь занимает ровно столько, сколько нужно её колонкам (первая
   + .availability-cell по 34px каждая), не более. */
.availability-grid {
  border-collapse: collapse;
  table-layout: fixed;
}

.availability-grid th {
  padding: 4px 6px;
  font-size: 0.8em;
  font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap;
  text-align: center;
}

.availability-grid th:first-child {
  width: 52px;
}

/* Два блочных элемента, а не одна строка "Пн 20.07" - день-колонки узкие
   (см. .availability-cell), полная надпись в один ряд туда не влезала бы
   при table-layout:fixed. */
.availability-grid-weekday,
.availability-grid-date {
  display: block;
}

.availability-grid-date {
  font-weight: 400;
  color: var(--text-secondary);
}

.availability-grid thead th {
  border-bottom: 1px solid rgba(var(--overlay-rgb), 0.15);
  padding-bottom: 6px;
}

.availability-grid tbody th {
  text-align: right;
  padding-right: 8px;
  color: var(--text-secondary);
  font-weight: 400;
}

/* Строка получасового слота за пределами границ дня, которые реально
   указал пользователь (see EventPlanner.search() - запрос сам по себе на
   час шире с каждой стороны, "-1ч"/"+1ч" контекст) - подпись времени
   приглушена, чтобы не путать с собственно запрошенным окном; сами ячейки
   всё так же кликабельны/раскрашены как обычно, это не какой-то отдельный
   недоступный режим. */
.availability-row-extended th {
  color: var(--text-dim);
  font-style: italic;
}

/* Стык между "лишним" часом и запрошенным окном (либо наоборот) - тонкая
   линия-разделитель на всю строку, чтобы граница была видна с первого
   взгляда, а не только по приглушённому цвету подписи выше. */
.availability-row-boundary th,
.availability-row-boundary td {
  border-top: 2px solid rgba(var(--overlay-rgb), 0.3);
}

.availability-cell {
  width: 38px;
  height: 22px;
  min-width: 38px;
  border: 1px solid rgba(var(--overlay-rgb), 0.08);
  cursor: pointer;
  transition: outline-color 0.1s ease;
}

.availability-cell:hover {
  outline: 2px solid var(--text-primary);
  outline-offset: -2px;
}

/* Ячейка, где занят хотя бы один "обязательный" участник - жёстко
   недоступна, кликом не выбирается вообще (см. EventPlanner.renderGrid,
   там на такой ячейке нет обработчика click). Тёмно-бордовый - тот же
   тёплый спектр, что и у доступных ячеек (зелёный..красный по score), но
   осознанно вынесенный за его пределы, а не серый: пользователь просил
   именно "heatmap по недоступности" для соседних слотов, а не резкий
   переход в нейтральный цвет. Штриховка - дополнительный небуквенный сигнал
   "недоступно" (не полагается только на цвет, для чуть better различимости
   при беглом взгляде на сетку). */
.availability-cell-blocked {
  background-color: var(--bg-danger-subtle);
  background-image: repeating-linear-gradient(
    135deg, rgba(var(--overlay-rgb), 0.06) 0, rgba(var(--overlay-rgb), 0.06) 2px,
    transparent 2px, transparent 6px
  );
  cursor: not-allowed;
}

.availability-cell-blocked:hover {
  outline: none;
}

/*****************************
       Events list page
******************************/
.events-layout {
  max-width: 800px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.events-card {
  background-color: var(--bg-surface);
  border: 1px solid rgba(var(--overlay-rgb), 0.08);
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  color: var(--text-primary);
  padding: 20px;
}

.events-card h2 {
  margin-top: 0;
  font-size: 1.1em;
}

/* Изначально только для Пользовательского соглашения/Политики
   конфиденциальности (terms.html/privacy.html) - обычный текст статьи
   внутри .events-card, а не список строк, поэтому заголовкам/абзацам/
   спискам нужны собственные отступы и line-height, которых базовый
   .events-card не задаёт. Переиспользуется и попапом-инструкцией
   "Как получить ссылку" на профиле (google-sync-info-overlay) - тот же
   набор "заголовок + абзацы + нумерованный список" внутри .modal-box,
   которому попап не задаёт собственную. */
.legal-page h3 {
  margin: 20px 0 8px;
  font-size: 1em;
}

.legal-page p,
.legal-page li {
  line-height: 1.5;
  color: var(--text-muted);
}

.legal-page ul,
.legal-page ol {
  margin: 4px 0;
  padding-left: 22px;
}

.legal-page li {
  margin-bottom: 6px;
}

.legal-page a {
  color: var(--accent);
}

.events-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}

.events-card-header h2 {
  margin-bottom: 0;
}

/* Группирует "Организовать событие" и "Предстоящие"/"Прошедшие" вместе -
   тот же приём, что и .details-header-actions на попапе события: без этой
   обёртки .events-card-header своим justify-content: space-between развело
   бы уже 3 элемента по всей ширине строки, а не оставило заголовок и
   действия ровно двумя половинами. flex-wrap - на случай, если обеим кнопкам
   вместе не хватает ширины карточки (см. мобильную .events-card-header ниже,
   которая и без этого уже переводит сам заголовок в отдельную строку). */
.events-card-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.events-organize-btn {
  white-space: nowrap;
  text-decoration: none;
}

/* Переключатель "Предстоящие"/"Прошедшие" на странице События (EventsList.js) -
   тот же градиент, что и у активного пункта верхней навигации (.page-nav-item.active). */
.events-time-toggle {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.events-time-toggle-btn {
  padding: 6px 14px;
  border: none;
  border-radius: 16px;
  background-color: rgba(var(--overlay-rgb), 0.08);
  color: var(--text-primary);
  cursor: pointer;
  font-size: 0.9em;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.events-time-toggle-btn.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
}

.group-create-panel {
  margin: 14px 0;
}

.group-create-panel.hidden {
  display: none;
}

.group-invite-username-form {
  display: flex;
  gap: 8px;
  margin-top: 12px;
  flex-wrap: wrap;
}

.group-invite-username-form input {
  flex: 1;
  min-width: 160px;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  background: rgba(var(--overlay-rgb), 0.05);
  color: inherit;
}

#group-detail-invite-username-message.hidden {
  display: none;
}

/* "Настройки группы" на group_detail.html (название/видимость/удаление) -
   тот же стиль label/input, что и .plan-form, просто не переиспользуя сам
   класс (тот задаёт ширину 320px/паддинг под карточку-сайдбар, тут это
   часть .event-detail-section во всю ширину). Раньше была спрятана за
   отдельной кнопкой-шестерёнкой внутри вкладки "Управление" - убрана как
   лишний второй уровень сворачивания поверх самой вкладки (см. её же
   комментарий в group_detail.html) - .group-settings-form больше никогда
   не получает класс .hidden, отдельное правило для него не нужно. */
.group-settings-form label {
  display: block;
  margin-bottom: 12px;
  font-size: 0.9em;
  font-weight: 500;
}

.group-settings-form input[type="text"],
.group-settings-form select,
.group-settings-form textarea {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 6px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
  box-sizing: border-box;
  resize: vertical;
}

/* Тот же вид, что и у .modal-box input[type="color"] (CategoryModal.js) -
   маленький квадратик-образец цвета, а не растянутый на всю ширину, как у
   текстовых полей выше. */
.group-settings-form input[type="color"] {
  display: block;
  margin-top: 4px;
  width: 48px;
  height: 32px;
  padding: 0;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: transparent;
}

/* Удаление группы - отдельный блок под остальными настройками, с явной
   границей сверху, чтобы визуально отделить необратимое действие от
   обычных полей выше. Кнопка "Удалить группу" начинается disabled (см.
   group_detail.html) - GroupDetail.js включает её, только когда введённый
   текст совпадает с точным названием группы. */
.group-delete-confirm-block {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid rgba(var(--overlay-rgb), 0.1);
}

.group-delete-confirm-block input[type="text"] {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 6px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
  box-sizing: border-box;
}

.details-delete:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.events-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Строка события (EventsList.js) - раньше "полотно текста", теперь чипы для
   даты/организатора/участников/тегов - тот же "красиво и подсвечивается при
   наведении" эффект, что и у .event-chip в месяце (transform+фон при hover).
   Дата - кликабельный <a>, ведёт на месяц с этим числом (?date=); остальные
   чипы - просто визуальные, без перехода. */
.events-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

.events-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 12px;
  background-color: rgba(var(--overlay-rgb), 0.08);
  font-size: 0.8em;
  white-space: nowrap;
  transition: transform 0.15s ease, background-color 0.15s ease;
}

a.events-chip {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

/* Чипы участников/организатора на странице "События" тоже кликабельны (см.
   ParticipantPreview.js) - обычные <span>, не <a>, поэтому cursor:pointer
   не наследуется от правила выше и нужен отдельно. */
.events-chip.participant-preview-trigger {
  cursor: pointer;
}

.events-chip:hover {
  background-color: rgba(var(--overlay-rgb), 0.18);
  transform: translateX(2px);
}

/* Цветной акцент строки на "События"/"Группы" (EventsList.js/GroupsList.js) -
   ev.category.color/group.color, проставляется по месту через инлайновую
   --row-color на каждой строке; здесь только сама отрисовка, общая для
   обоих (класс .events-row один и тот же в обоих файлах). Не задана -
   var() падает на transparent, так что остальные страницы с .events-row
   (уведомления, "Требующие подтверждения"), которые эту переменную не
   ставят, выглядят как прежде. color-mix - лёгкая тонировка вместо
   сплошной заливки, чтобы не перебивать текст поверх произвольного (в том
   числе яркого) цвета. */
.events-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 14px;
  border-radius: 6px;
  border-left: 4px solid var(--row-color, transparent);
  background-color: rgba(var(--overlay-rgb), 0.04);
  background-image: linear-gradient(90deg, color-mix(in srgb, var(--row-color, transparent) 16%, transparent) 0%, transparent 60%);
  transition: background-color 0.15s ease;
}

/* Непрочитанное уведомление (schedule/notifications.html) - тот же приём,
   что и у .event-detail-note (левая цветная граница), только акцентным
   синим вместо нейтрального серого, раз это "новое", а не просто заметка. */
.events-row.notification-unread {
  border-left: 3px solid var(--accent);
  background-color: rgba(var(--accent-rgb), 0.08);
}

/* Переход на страницу события - клик по самой строке (EventsList.js/
   GroupsList.js, см. .events-row-clickable в CLAUDE.md), не отдельная
   кнопка-ссылка. По запросу ("может попробовать немного увеличивать
   карточку... а не только подсвечивать") подсветка фоном дополнена лёгким
   увеличением - тем же приёмом "transform + фон на hover", что и у
   .event-chip/.events-chip выше, только scale() вместо translateX(): те
   два - узкие чипы, для которых сдвиг вбок читается как "поднялся";
   .events-row - на всю ширину карточки, где сдвиг в сторону не имел бы
   смысла, а лёгкое увеличение прямо на месте выглядит как "приподнялась
   под курсором", не задевая соседние строки (10px gap у .events-list и
   20px padding у .events-card с запасом гасят разницу в размере при таком
   масштабе - scale(1.02), не бóльший, чтобы это не читалось как готовность
   "выпрыгнуть" из списка). Добавленная тень усиливает то же ощущение
   приподнятости, а не просто более яркого фона. */
.events-row-clickable {
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.events-row-clickable:hover {
  background-color: rgba(var(--overlay-rgb), 0.09);
  transform: scale(1.02);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
  /* position+z-index - иначе увеличенная строка (и особенно её тень) могла
     бы частично закрашиваться следующей по порядку строкой, у которой
     собственного transform/hover сейчас нет: элементы в обычном потоке без
     transform красятся строго по DOM-порядку, а transform сам по себе не
     поднимает элемент над соседями без явного z-index. */
  position: relative;
  z-index: 1;
}

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

.events-row-title-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}

.events-row-title {
  font-weight: bold;
}

.events-row-meta {
  font-size: 0.85em;
  color: var(--text-muted);
}

.events-row-badge {
  flex-shrink: 0;
  font-size: 0.85em;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* Список участников (events.html/EventsList.js) - <details> вместо обычной
   строки, своя иконка-шеврон вместо нативного треугольника-маркера (тот
   слишком незаметен на тёмном фоне и не в стиле остальных SVG-иконок
   приложения). */
.events-participants-details summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 4px;
}

.events-participants-details summary::-webkit-details-marker,
.events-participants-details summary::marker {
  display: none;
}

.events-participants-chevron {
  display: inline-flex;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}

.events-participants-chevron svg {
  width: 12px;
  height: 12px;
}

.events-participants-details[open] .events-participants-chevron {
  transform: rotate(90deg);
}

.events-participants-body {
  margin-top: 4px;
}

.events-row-wrapper {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.events-invite-toggle {
  flex-shrink: 0;
  padding: 7px 14px;
  border: 1px solid rgba(var(--overlay-rgb), 0.1);
  border-radius: 20px;
  background-color: rgba(var(--overlay-rgb), 0.08);
  color: var(--text-primary);
  cursor: pointer;
  font: inherit;
  font-size: 0.85em;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.events-invite-toggle:hover {
  background-color: rgba(var(--overlay-rgb), 0.18);
  transform: translateY(-1px);
}

.events-invite-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px;
  border-radius: 6px;
  background-color: rgba(var(--overlay-rgb), 0.03);
  border: 1px dashed rgba(var(--overlay-rgb), 0.12);
}

.events-invite-panel.hidden {
  display: none;
}

.events-invite-link-row {
  display: flex;
  gap: 8px;
}

.events-invite-link {
  flex: 1;
  min-width: 0;
  padding: 6px;
  border: 1px solid rgba(var(--overlay-rgb), 0.15);
  border-radius: 4px;
  background-color: var(--bg-surface-2);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.85em;
}

.events-invite-copy,
.events-invite-qr-toggle {
  flex-shrink: 0;
  padding: 6px 12px;
  font-size: 0.85em;
  font-weight: normal;
}

/* Открывает t.me/<bot>?startapp=... - см. CLAUDE.md "Общий доступ через
   Telegram". Фирменный телеграмный синий (#2AABEE, не токен темы) -
   намеренно узнаваемый брендовый цвет, а не переиспользование --accent,
   чтобы кнопка читалась как "это откроет именно Telegram", а не как ещё
   одно обычное действие в общем акцентном цвете приложения. */
.events-invite-telegram-link {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: 0.85em;
  font-weight: normal;
  background-color: #2aabee;
  border-color: #2aabee;
  color: #fff;
  text-decoration: none;
}

.events-invite-telegram-link:hover {
  background-color: #229ed9;
  border-color: #229ed9;
}

.events-invite-qr-toggle {
  align-self: flex-start;
}

.events-invite-qr {
  width: 150px;
  height: 150px;
  /* Не через переменные темы - сам QR-код (см. qrcode.make в views.py)
     генерируется чёрным по белому и должен оставаться читаемым сканером
     независимо от темы сайта, белая подложка тут функциональная, не
     декоративная. */
  background-color: #fff;
  padding: 8px;
  border-radius: 6px;
  align-self: flex-start;
}

.events-invite-qr.hidden {
  display: none;
}

/*****************************
       Profile page
******************************/
.profile-avatar-row {
  display: flex;
  justify-content: center;
  margin-bottom: 16px;
}

.profile-avatar-preview {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid rgba(var(--overlay-rgb), 0.15);
}

.profile-avatar-placeholder {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2em;
  font-weight: bold;
  color: white;
}

/*****************************
       Styled file input (аватар, картинка события)
******************************/
/* Нативная <input type="file"> кнопка ("Choose File") не подчиняется теме
   сайта вообще (рисуется самим браузером/ОС) - визуально прячем её (не
   display:none - тогда бы совсем выпала из доступности/фокуса по Tab), а
   реальная кнопка - соседний .file-input-button, клик по нему открывает
   системный диалог сам, потому что оба лежат в одном <label>. См.
   FileInput.js - только показывает имя выбранного файла рядом. */
.file-input-styled {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
}

.file-input-native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.file-input-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: 10px;
  background-color: var(--bg-surface-2);
  border: 1px solid rgba(var(--overlay-rgb), 0.1);
  color: var(--text-primary);
  font-weight: 500;
  font-size: 0.95em;
  transition: background-color 0.15s ease;
}

.file-input-styled:hover .file-input-button {
  background-color: var(--bg-header);
}

.file-input-button svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.file-input-filename {
  font-size: 0.85em;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 220px;
}

/*****************************
       Mobile (узкие экраны)
******************************/
@media (max-width: 600px) {
  /* Верхняя навигация целиком уступает место нижней панели с иконками
     (.bottom-nav, ниже) - раньше здесь просто разрешался перенос строк, но
     кнопка-переключатель второстепенного меню сама уезжала на вторую строку,
     а её выпадающее меню (позиционированное относительно неё) вместе с ней -
     за левый край экрана. Два разных, не переиспользуемых компонента вместо
     одного адаптивного устраняют эту категорию багов целиком. */
  .page-nav {
    display: none;
  }

  .bottom-nav {
    display: flex;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1600;
    background-color: var(--bg-surface-2);
    border-top: 1px solid rgba(var(--overlay-rgb), 0.08);
    padding: 6px 4px;
    padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
  }

  .bottom-nav-item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 4px 2px;
    color: var(--text-secondary);
    text-decoration: none;
    background: transparent;
    border: none;
    font: inherit;
    cursor: pointer;
  }

  /* SVG вместо эмодзи - разные платформы рисуют эмодзи-календари/людей в
     совершенно разных стилях и цветах (не сочетаются друг с другом и с
     тёмной темой). currentColor у svg means .active раскрашивает иконку
     так же, как и текст под ней - без отдельного правила на сам svg. */
  .bottom-nav-icon svg {
    width: 22px;
    height: 22px;
    display: block;
  }

  .bottom-nav-label {
    font-size: 0.65em;
  }

  .bottom-nav-item.active {
    color: var(--accent);
  }

  /* Форма - полноценный bottom sheet (явный ask - "форма панели ближе к
     нативному листу, а не карточке над таббаром"): во всю ширину
     (left/right: 0, вместо прежних 12px отступов), скруглены только верхние
     углы. Позиция - вплотную над .bottom-nav, а не перекрывая её целиком до
     самого края экрана (см. её же историю выше) - --bottom-nav-height
     задаётся в JS (updateBottomNavHeightVar в nav-index.js) через реальный
     getBoundingClientRect().height самой .bottom-nav, а не подобранным на
     глаз числом (тем же приёмом, что и updatePageNavLogoVisibility в этом
     же файле - "измеряем реальное состояние DOM, а не гадаем пиксели") -
     сама .bottom-nav уже учитывает свой собственный safe-area-inset-bottom
     в собственном padding, так что измеренная высота уже includes его, без
     двойного учёта. Реальный найденный баг: раньше здесь стояло жёстко
     подобранное число (64px) без замера - оно недооценивало настоящую
     высоту .bottom-nav ровно настолько, что нижний край шторки заезжал за
     верхний край таббара, из-за чего шторка визуально "уходила поверх"
     таббара вместо того, чтобы аккуратно стоять над ним - хотя по коду это
     не был тот же самый "закрывает весь таббар" баг из предыдущей версии
     правки, выглядело оно похоже, потому и было воспринято как та же самая
     проблема. 64px в fallback-значении var() - на случай, если JS ещё не
     успел выполниться (первая отрисовка) или отключён вовсе - тот же
     подобранный, но уже не единственный источник истины ориентир, что и
     раньше. */
  .bottom-nav-panel {
    /* Отменяет десктопный дефолт display:none (см. categories.css выше,
       "РЕАЛЬНЫЙ найденный баг") - только здесь, внутри мобильного
       media query, панель "Ещё" вообще должна что-то показывать. */
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--bottom-nav-height, 64px);
    /* Явный ask - "слишком сильный контраст цвета окошка и нижней панели":
       общее правило .nav-menu-dropdown/.bottom-nav-panel выше красит фон в
       --bg-surface (тот же тон, что и у большинства попапов приложения) -
       уместно, пока панель парит отдельно от .bottom-nav (та красится в
       --bg-surface-2, на тон темнее/светлее в зависимости от темы), но раз
       теперь они стоят вплотную друг к другу без зазора, разница тонов
       читалась как жёсткий шов на стыке. Перекрашено в тот же --bg-surface-2,
       что и у самой .bottom-nav - обе читаются как одна сплошная
       поверхность, а не два наложенных друг на друга разных слоя. */
    background-color: var(--bg-surface-2);
    border: none;
    border-top: 1px solid rgba(var(--overlay-rgb), 0.08);
    border-radius: 16px 16px 0 0;
    padding: 4px 14px 10px;
    /* Явный ask - "осталась только частично излишняя тень": общее правило
       .nav-menu-dropdown/.bottom-nav-panel выше несёт box-shadow (0 8px
       24px), рассчитанную на десктопное меню, парящее с зазором над
       контентом страницы - там тень читается как "приподнятость". Здесь же
       шторка стоит вплотную к .bottom-nav (см. bottom: var(--bottom-nav-height)
       выше), без единого пикселя зазора между ними, и та же самая тень (со
       сдвигом вниз на 8px и растяжкой в 24px) утекала прямо на саму
       таббар-панель под ней - визуально читалось как лишнее затемнение
       именно в этом одном месте, а не как эффект "лист приподнят над
       страницей". border-top выше уже достаточно отделяет шторку от
       контента страницы над ней, так что отдельная тень тут не нужна
       вообще - убираем её только для мобильного bottom sheet, десктопное
       .nav-menu-dropdown (у которого зазор реально есть) не трогаем. */
    box-shadow: none;
    /* Явный ask - "жест внутри окна работает как скроллинг страницы":
       без этого браузер сам решал, что вертикальное перетягивание внутри
       .bottom-nav-panel - это попытка прокрутить страницу (панель сама не
       скроллится, overflow не задан, так что жест "утекал" на ближайший
       реально скроллящийся контейнер - документ), быстрее, чем успевал
       сработать preventDefault() внутри touchmove в initSwipeToDismiss.
       Тот же приём, что и у .bottom-nav-panel-handle-area/.bottom-nav-backdrop
       ниже - теперь перетягивание в любом месте панели (не только по
       хвату) уходит в initSwipeToDismiss, а не в нативный скролл. */
    touch-action: none;
  }

  /* Зона свайпа вниз для закрытия (см. initNavMenu в NavMenu.js) - явный
     ask ("возможность сворачивать перетягиванием вниз"). Сама -area шире и
     выше видимой полоски (36×4px) специально ради тап-таргета - настоящий
     хват такого размера сам по себе слишком мал, чтобы надёжно попасть по
     нему пальцем; отрицательный margin компенсирует добавленный этой зоной
     padding, чтобы визуальная полоска осталась на том же месте, что и была
     без неё. */
  .bottom-nav-panel-handle-area {
    display: flex;
    justify-content: center;
    padding: 10px 0 6px;
    margin: -4px 0 -2px;
    touch-action: none;
  }

  .bottom-nav-panel-handle {
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background-color: rgba(var(--overlay-rgb), 0.25);
  }

  /* Подложка позади шторки "Ещё" (явный ask - "чтобы нельзя было листать
     фон, свайп должен работать как сворачивание окна") - тот же принцип,
     что и у .modal-overlay для остальных попапов приложения (полноэкранный
     затемняющий слой, перехватывающий события), только z-index ниже, чем у
     .bottom-nav (1600 - см. её же правило выше) - специально, чтобы сама таббар оставалась видимой
     и кликабельной ПОВЕРХ подложки, пока шторка открыта (см. её же историю
     выше - таббар не должен становиться недоступным). Раз подложка -
     обычный сплошной div над всей остальной страницей, она сама по себе (по
     умолчанию, без единой лишней строчки JS) уже не даёт пальцу докрутить
     скролл до контента под ней - никакого overflow:hidden на body не
     потребовалось. touch-action: none - страховка от того, что браузер
     всё равно попытается интерпретировать жест на ней как скролл страницы
     ДО того, как успеет отработать preventDefault() в touchmove (см.
     initSwipeToDismiss) - тот же приём, что и у .bottom-nav-panel-handle-area
     выше. Открытие/закрытие - тот же opacity-переход, что и у самой
     .bottom-nav-panel (см. её .hidden выше), в ту же секунду. */
  .bottom-nav-backdrop {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    /* Явный ask - "оно всё ещё поверх нижней панельки": раньше здесь стоял
       inset:0 (то есть bottom:0), из-за чего подложка накрывала собой и
       область самого таббара тоже - "не просвечивает" эта область только
       потому, что .bottom-nav красится поверх неё за счёт более высокого
       z-index (1400 против 1350 у подложки). Это работало, только пока оба
       значения (нижний край подложки и нижний край шторки/.bottom-nav-panel,
       см. bottom: var(--bottom-nav-height) выше) СОВПАДАЛИ пиксель в
       пиксель - при малейшем расхождении (округление при замере реальной
       высоты .bottom-nav в JS, разный момент выполнения) в шве между
       шторкой и таббаром на мгновение проглядывала сама подложка -
       затемнённая полоска ровно там, где она заметнее всего. Вместо того
       чтобы полагаться на z-index, который "случайно" перекрывает лишнюю
       область, подложка теперь просто не рисуется в этой области вовсе -
       тот же bottom: var(--bottom-nav-height), что и у самой .bottom-nav-panel
       (то же значение, вычисленное JS'ом один раз - обе используют одну и
       ту же переменную, так что расхождению просто неоткуда взяться). */
    bottom: var(--bottom-nav-height, 64px);
    z-index: 1350;
    background-color: rgba(0, 0, 0, 0.5);
    opacity: 1;
    transition: opacity 0.18s ease;
    touch-action: none;
  }

  .bottom-nav-backdrop.hidden {
    opacity: 0;
    pointer-events: none;
  }

  /* Место под фиксированную нижнюю панель, чтобы контент не прятался под ней */
  .container {
    padding-bottom: 76px;
  }

  /* Иначе баннер режима проставления перекрывается нижней панелью навигации
     (.bottom-nav, тоже position:fixed) - поднимаем его выше. */
  .template-apply-banner {
    bottom: 70px;
  }

  /* Боковые отступы .week-card (15px на десктопе, см. выше) уменьшены вдвое
     на мобильном - на узком экране они сами по себе заметно "съедают" и без
     того тесную неделю из 7 колонок. Только left/right (по просьбе "по
     бокам") - верх/низ остаются как есть, .container уже даёт им отдельный
     небольшой отступ. */
  .week-card {
    padding-left: 7.5px;
    padding-right: 7.5px;
  }

  /* Неделя: desktop-овская ширина колонки (80px * 7 + метка времени) не
     влезает в экран телефона - вместо фиксированной ширины даём каждой из 7
     колонок дней width:auto на таблице шириной 100% контейнера, так
     table-layout:fixed делит остаток поровну между всеми 7, независимо от
     длины текста в шапке (короткие Пн/Вт/... и так не отличаются по длине,
     но ширина колонки раньше зависела от содержимого другого дня в неделе,
     когда была всего одна видимая колонка - см. CLAUDE.md). Боковые border
     (0.5em на десктопе) урезаны, а не обнулены совсем - иначе 7 колонок
     сливаются в одну сплошную полосу без видимого зазора между днями. */
  .planner {
    width: 100%;
  }

  /* Подпись времени (00h, 02h, ...) прижата к ПРАВОМУ краю своей колонки
     (см. .planner tbody tr td:nth-child(1) span в styles.css - right: 0, а
     не left: 0, чтобы подпись читалась рядом с самой сеткой). На десктопе
     ширина этой колонки (2em) погоды не делает, но на телефоне тот же
     фиксированный 2em - заметно бОльшая доля узкого экрана, и пустое место
     слева от текста (внутри самой колонки, до её начала) читается как
     подозрительно большой отступ. Более узкая колонка - меньше пустого места
     перед текстом, при этом "00h" по-прежнему не обрезается. */
  .planner thead tr th:first-child,
  .planner tbody tr td:first-child {
    width: 1.5em;
  }

  .planner thead tr th:not(:first-child),
  .planner tbody tr td:not(:first-child) {
    width: auto;
    border-left-width: 2px;
    border-right-width: 2px;
    /* Тот же разделитель дней, что и на десктопе (см. styles.css), но
       заметно контрастнее - на 2px (вместо десктопных 0.5em) та же прозрачность
       0.1 читается почти как отсутствие линии вовсе, а не как раздельные дни. */
    border-left-color: rgba(var(--overlay-rgb), 0.22);
    border-right-color: rgba(var(--overlay-rgb), 0.22);
  }

  .day-name {
    font-size: 0.8em;
  }

  .planner tbody tr td {
    border-radius: 0;
    border-top-width: 0;
    border-bottom: 1px solid rgba(var(--overlay-rgb), 0.05);
  }

  .planner tbody tr:nth-child(4n) td {
    border-bottom-color: rgba(var(--overlay-rgb), 0.14);
  }

  .plan-layout {
    flex-direction: column;
  }

  .plan-form {
    width: 100%;
  }

  /* align-items:center из десктопной версии ставит бейдж/кнопку по центру
     сбоку от многострочного (на узком экране почти всегда) названия -
     колонка вместо переноса строки убирает наложение. */
  .events-row {
    flex-direction: column;
    align-items: flex-start;
  }

  /* .events-card-header (заголовок + .events-time-toggle - "Предстоящие"/
     "Прошедшие" на /events/, "Список"/"Карта доступности" на /plan/) на
     десктопе - flex-ряд с justify-content:space-between, но
     .events-time-toggle сам себе задаёт flex-shrink:0 (не должен сжиматься
     до нечитаемого размера), а многословный заголовок ("Групповые события")
     всё равно занимает какую-то минимальную ширину - на экране шириной с
     iPhone их суммы не хватает места внутри .events-card (padding 20px с
     каждой стороны), из-за чего переключатель вылезал правым краем за рамку
     карточки. Колонка вместо ряда - тот же приём, что и у .events-row выше. */
  .events-card-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .events-invite-link-row {
    flex-wrap: wrap;
  }

  .modal-box {
    padding: 16px 18px;
  }

  /* Месяц: те же чипы, что на десктопе (см. .event-chip/.event-chip-time/
     .event-chip-title выше) - просто время и название переносятся на разные
     строки вместо однострочного обрезания, раз по высоте места намного
     больше, чем по ширине. Кнопки "+" на ячейках нет - создание события
     теперь по тапу на саму ячейку (см. MonthView.js).

     Ведущая колонка со стрелкой "открыть неделю" (28px) на телефоне убрана
     целиком - вместо нижнего ряда через неделю проще просто открыть Неделю
     из нижней панели; занятые ею 28px отдаём дням, чтобы клетки были на всю
     ширину экрана. Отступы между клетками (gap) убраны - вместо них теперь
     просто линия (border самой .month-day), как в настоящем календаре, а не
     карточки с промежутками. */
  .month-layout {
    padding: 4px;
    border: none;
    box-shadow: none;
    border-radius: 0;
  }

  .month-header {
    gap: 10px;
    margin-bottom: 8px;
  }

  /* Явный ask - "пригвоздить в правый угол": на мобильном шестерёнка (см.
     schedule/_calendar_header_actions.html) снова абсолютно позиционирована
     в углу карточки, точно как на десктопе (нужный position:relative
     контекст по-прежнему даёт .week-card/.month-layout, а не сам
     .week-header/.month-header - тому position вообще не задан) - раньше,
     между этой версией и самой первой, она на один релиз становилась
     обычным 4-м flex-элементом строки .week-header/.month-header, на одном
     уровне со стрелками, но от этого отказались обратно в пользу
     фиксированного угла. Раз кнопка снова "плавающий блок" над стрелками/
     заголовком, а не часть их строки, обеим шапкам ниже нужен свой отступ
     сверху, чтобы не перекрываться с ней. */
  .calendar-header-actions {
    gap: 6px;
  }

  .week-header,
  .month-header {
    margin-top: 30px;
  }

  .calendar-header-actions .details-link {
    width: 26px;
    height: 26px;
  }

  /* Та же логика сжатия, что и у соседней .details-link выше - на узком
     экране рядом со стрелками/заголовком нет места ещё и на текст "Организовать
     событие", остаётся только иконка + title-тултип (тот же 26px-круг). */
  .calendar-organize-btn-label {
    display: none;
  }

  .calendar-organize-btn {
    width: 26px;
    height: 26px;
    padding: 0;
    justify-content: center;
  }

  .month-title {
    font-size: 1.1em;
    min-width: 0;
  }

  .month-weekdays,
  .month-grid {
    grid-template-columns: repeat(7, 1fr);
  }

  .month-weekdays > div:first-child {
    display: none;
  }

  .month-week-nav {
    display: none;
  }

  .month-grid {
    grid-auto-rows: minmax(116px, auto);
    gap: 0;
  }

  .month-day {
    padding: 2px;
    gap: 1px;
    cursor: pointer;
    border-radius: 0;
  }

  .month-day-number {
    font-size: 0.85em;
  }

  .month-day .add-event-btn {
    display: none;
  }

  /* Как и на десктопе - фиксированная высота + внутренний скролл при
     переполнении, а не "пусть строка сетки растёт" (иначе один загруженный
     день делает высокой всю строку недели целиком, а не только свою ячейку). */
  .month-day-events {
    max-height: 88px;
  }

  .event-chip {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 3px 5px;
  }

  .event-chip-time {
    font-size: 0.9em;
  }

  /* Название разрешаем переносить (места по высоте достаточно), но не
     бесконечно - клэмп в 2 строки с многоточием, чтобы одно длинное
     название не съело всю оставшуюся высоту ячейки. */
  .event-chip-title {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .month-week-nav {
    font-size: 0.9em;
  }

  /* Явный ask - "верхний навигационный элемент немного выбивается": на
     мобильном .profile-jumpnav - единственный липкий элемент в самом верху
     экрана (десктопная .page-nav тут display:none, см. выше, а .bottom-nav
     сидит внизу) - его 7 пунктов при обычном flex-wrap заворачивались в 2
     полноширинные строки, из-за чего скруглённая "пилюля" превращалась в
     плотный прямоугольный блок почти во всю ширину экрана, читавшийся как
     ещё одна (вторая) панель навигации сайта, а не лёгкая внутристраничная
     мини-навигация. Одна прокручиваемая по горизонтали строка вместо
     переноса - тот же принадлежащий этому же приложению паттерн, что и
     сама .bottom-nav (компактная однострочная лента), только со скроллом
     вместо фиксированного набора вкладок. Скроллбар спрятан (тач-скролл
     не нуждается в видимом индикаторе, тот же приём уже применён к
     .month-day-events/.modal-box) - подсказкой, что тут ещё есть куда
     скроллить, служит сама обрезка последнего видимого пункта по краю. */
  .profile-jumpnav {
    flex-wrap: nowrap;
    overflow-x: auto;
    -ms-overflow-style: none;
    scrollbar-width: none;
  }

  .profile-jumpnav::-webkit-scrollbar {
    display: none;
  }

  .profile-jumpnav a {
    flex-shrink: 0;
  }
}

/*****************************
       Быстрый старт
******************************/
/* schedule/quick_start.html - экспериментальная страница-витрина, см.
   quick_start_view/CLAUDE.md: нигде не связана из навигации, буквально
   несколько крупных кнопок-ярлыков по центру экрана. .quick-start-hero
   центрирует всё flex'ом по обеим осям, а не просто отступами сверху -
   раз на странице ничего больше нет, содержимое должно оставаться по
   центру независимо от высоты окна/добавления description-текста в
   будущем. min-height вычитает примерную высоту .page-nav/.bottom-nav
   ПЛЮС дополнительный запас (260px, было 220px, а до того 140px - второй
   явный запрос "поднять весь блок ещё немного повыше" подряд, тот же
   приём, ещё один шаг): уменьшая собственную высоту .quick-start-hero (а
   не просто уменьшая верхний отступ), justify-content: center центрирует
   содержимое в меньшей области, и визуальная середина сетки кнопок
   сдвигается ближе к шапке, а не к центру всего окна. */
.quick-start-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - 260px);
  padding: 10px 0px 0px 0px;
  text-align: center;
}

.quick-start-title {
  margin: 0 0 32px;
  font-size: 1.8em;
  color: var(--text-primary);
}

.quick-start-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(180px, 220px));
  gap: 24px;
}

@media (max-width: 600px) {
  .quick-start-grid {
    grid-template-columns: minmax(0, 260px);
  }
}

.quick-start-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 32px 20px;
  border-radius: 16px;
  background-color: var(--bg-surface-2);
  border: 1px solid rgba(var(--overlay-rgb), 0.1);
  color: var(--text-primary);
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
}

.quick-start-btn:hover {
  transform: translateY(-3px);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
  background-color: var(--bg-header);
}

/* Тот же акцентный градиент, что и у .event-save/.event-group-switch-thumb
   (см. CLAUDE.md) - переиспользуется, а не изобретается новый цветовой
   приём специально для этой страницы. */
.quick-start-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  flex-shrink: 0;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  color: white;
  box-shadow: 0 2px 8px rgba(var(--accent-solid-rgb), 0.5);
}

.quick-start-icon svg {
  width: 32px;
  height: 32px;
}

.quick-start-label {
  font-size: 1.05em;
  font-weight: 600;
}

/*****************************
       Focus ring (доступность)
******************************/
button:focus-visible,
a:focus-visible,
select:focus-visible,
input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
