/*****************************
       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;
}

.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 {
  display: flex;
  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;
}

.page-nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
}

.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;
}

.page-nav-item:hover {
  background-color: rgba(var(--overlay-rgb), 0.06);
  color: var(--text-primary);
}

.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-spacer {
  flex-grow: 1;
}

/*****************************
       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,
.bottom-nav-panel.hidden {
  display: 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 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;
}

.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 updateRepeatSectionVisibility) - без
   этого правила .hidden добавлялся на .event-repeat-section, но ничего
   реально не скрывал (нет общего .hidden{display:none} на всё приложение -
   у каждого прячущегося компонента своё явное правило), так что кнопка
   "Повтор" оставалась видимой и кликабельной даже для группового события,
   для которого repeat всё равно принудительно 'none' на бэкенде. */
.event-repeat-section.hidden {
  display: none;
}

/* Пригласить участников показывается/прячется сразу целиком (см.
   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;
}

/* Пикер приглашения участников (см. 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);
}

/* Кнопка блокируется сразу по submit (см. FormGuard.js) - защита от
   повторной отправки формы двойным кликом/двойным Enter, например у формы
   комментария (.event-comment-form). Тот же вид, что и у .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 {
  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;
}

.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;
}

/* "Про"-пользователь (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
   двигает её при перетаскивании/зуме. */
.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;
  width: 260px;
  height: 260px;
  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;
}

.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);
}

/* "Особая пометка" модератора группы (см. 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;
}

.group-member-action-link {
  display: block;
  font-size: 0.78em;
  color: var(--accent);
  text-decoration: none;
  margin-top: 2px;
}

.group-member-action-link:hover {
  text-decoration: underline;
}

.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;
}

.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);
}

.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;
}

/*****************************
       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;
}

/* .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;
}

/* Переключатель "Предстоящие"/"Прошедшие" на странице События (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-settings-toggle, см. GroupDetail.js) -
   не через .event-detail-section.hidden (та скрыла бы и сам заголовок с
   кнопкой), а через собственное правило прямо на .group-settings-form,
   чтобы заголовок с кнопкой-переключателем оставался всегда виден. */
.group-settings-form.hidden {
  display: none;
}

.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), не
   отдельная кнопка-ссылка - тот же "подсветка при наведении" эффект, что и
   у .event-chip/.events-chip. */
.events-row-clickable {
  cursor: pointer;
}

.events-row-clickable:hover {
  background-color: rgba(var(--overlay-rgb), 0.09);
}

.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;
}

.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: 1400;
    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-nav-panel {
    position: fixed;
    left: 12px;
    right: 12px;
    bottom: 64px;
  }

  /* Место под фиксированную нижнюю панель, чтобы контент не прятался под ней */
  .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;
  }

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

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

  .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;
  }
}

/*****************************
       Быстрый старт
******************************/
/* schedule/quick_start.html - экспериментальная страница-витрина, см.
   quick_start_view/CLAUDE.md: нигде не связана из навигации, буквально
   несколько крупных кнопок-ярлыков по центру экрана. .quick-start-hero
   центрирует всё flex'ом по обеим осям, а не просто отступами сверху -
   раз на странице ничего больше нет, содержимое должно оставаться по
   центру независимо от высоты окна/добавления description-текста в
   будущем. min-height вычитает примерную высоту .page-nav/.bottom-nav
   ПЛЮС дополнительный запас (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 - 220px);
  padding: 40px 20px;
  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;
}
