Files
vidconf/frontend/src/styles/shell.css
Max Ronzhin 07e5aba9c9 refactor(calendar): форма создания/редактирования конференции — в модальное окно
Раньше ConferenceFormCard всегда висела в сайдбаре двухколоночной сетки
.layout-grid; теперь открывается в модалке (по образцу
ConferenceOccurrenceDialog: modal-overlay/modal-panel, закрытие по клику на
оверлей, Escape и возврат фокуса — новый общий хук useModalDismiss).
.layout-grid стала одноколоночной — календарь занимает всю ширину.
2026-07-26 23:33:52 +03:00

498 lines
17 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* VidConf — общие стили светлой оболочки (auth/lobby/calendar/admin).
* 1:1 воспроизводит классы из design/mockups/{auth,lobby}.html, чтобы
* JSX-разметка страниц могла использовать те же имена классов, что и
* утверждённые макеты. Токены — из tokens.css.
*/
/* ---------- Топбар (лобби/календарь/админка) ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) var(--space-8);
background: var(--color-surface);
border-bottom: 1px solid var(--color-border);
}
.brand-mark {
display: inline-flex;
align-items: center;
gap: 8px;
font: var(--text-h3);
color: var(--color-ink-700);
text-decoration: none;
}
.brand-mark .logo-mark {
width: 22px;
height: 22px;
flex-shrink: 0;
}
.topbar-right {
display: flex;
align-items: center;
gap: var(--space-4);
}
/* ---------- Переключатель темы «солнце/луна» (см. design/mockups/dark/*.html) ---------- */
.theme-toggle {
display: inline-flex;
gap: 2px;
background: var(--color-bg-alt);
padding: 4px;
border-radius: var(--radius-full);
border: 1px solid var(--color-border);
}
.theme-toggle-btn {
width: 32px;
height: 32px;
border-radius: 50%;
border: none;
background: transparent;
color: var(--color-ink-500);
display: flex;
align-items: center;
justify-content: center;
}
.theme-toggle-btn .icon {
width: 16px;
height: 16px;
}
.theme-toggle-btn:hover {
color: var(--color-ink-900);
}
.theme-toggle-btn.is-active {
background: var(--color-surface);
color: var(--color-ink-700);
box-shadow: var(--shadow-sm);
}
/* ---------- Пилюля пользователя (декоративная, см. lobby.html) ---------- */
.user-pill {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 6px 16px 6px 6px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-full);
font: var(--text-body);
font-weight: 600;
color: var(--color-ink-900);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.user-pill:hover {
border-color: var(--color-border-strong);
box-shadow: var(--shadow-sm);
}
.avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--color-ink-700);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font: var(--text-caption);
text-transform: none;
letter-spacing: 0;
flex-shrink: 0;
overflow: hidden;
}
/* Фото аватара (Avatar.tsx) — покрывает заглушку с инициалами целиком. */
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
.user-pill .avatar {
width: 30px;
height: 30px;
}
/* ---------- Бургер и скрывающаяся панель навигации (см. lobby.html) ---------- */
.menu-wrap {
position: relative;
}
.burger {
width: 44px;
height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
color: var(--color-ink-700);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.burger:hover {
border-color: var(--color-border-strong);
box-shadow: var(--shadow-sm);
}
.burger[aria-expanded='true'] {
border-color: var(--color-ink-700);
}
.menu {
position: absolute;
top: calc(100% + 10px);
right: 0;
min-width: 236px;
padding: var(--space-2);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
display: none;
z-index: 30;
}
.menu.is-open {
display: block;
}
.menu a,
.menu button {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 10px 14px;
border: none;
background: transparent;
border-radius: var(--radius-sm);
font: var(--text-body-lg);
font-weight: 600;
color: var(--color-ink-900);
text-decoration: none;
text-align: left;
cursor: pointer;
}
.menu a:hover,
.menu button:hover {
background: var(--color-bg-alt);
color: var(--color-ink-700);
}
.menu a.is-active {
background: var(--color-bg-alt);
color: var(--color-ink-700);
}
.menu a .icon,
.menu button .icon {
color: var(--color-ink-400);
}
.menu-divider {
height: 1px;
background: var(--color-border);
margin: var(--space-2) 6px;
}
.menu a.danger,
.menu button.danger {
color: var(--color-danger);
}
.menu a.danger .icon,
.menu button.danger .icon {
color: var(--color-danger);
}
/* ---------- Топбар на узких экранах ---------- */
@media (max-width: 640px) {
.topbar {
padding: var(--space-3) var(--space-4);
}
.topbar-right {
gap: var(--space-2);
}
.user-pill {
padding: 6px;
gap: 0;
}
.user-pill-name {
display: none;
}
}
/* ---------- Кнопки (общие для оболочки) ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: none;
cursor: pointer;
font-family: var(--font-body);
white-space: nowrap;
transition: background var(--transition-fast), box-shadow var(--transition-fast);
}
.btn-primary {
background: var(--color-accent);
color: var(--color-accent-text);
font: var(--text-body);
font-weight: 700;
padding: 11px 22px;
border-radius: var(--radius-full);
}
.btn-primary:hover:not(:disabled) {
background: var(--color-accent-hover);
box-shadow: var(--shadow-md);
}
.btn-primary:active:not(:disabled) {
background: var(--color-accent-active);
}
.btn-primary:disabled {
background: rgba(166, 174, 203, 0.35);
color: var(--color-surface);
cursor: not-allowed;
}
.btn-secondary {
background: transparent;
color: var(--color-ink-700);
border: 1.5px solid var(--color-border-pill);
font: var(--text-body);
font-weight: 600;
padding: 9px 18px;
border-radius: var(--radius-full);
}
.btn-secondary:hover:not(:disabled) {
background: var(--color-bg-alt);
}
.btn-secondary:disabled {
border-color: var(--color-border);
color: var(--color-ink-300);
cursor: not-allowed;
}
.btn-danger {
background: transparent;
color: var(--color-danger);
border: 1.5px solid var(--color-danger);
font: var(--text-body);
font-weight: 600;
padding: 9px 18px;
border-radius: var(--radius-full);
}
.btn-danger:hover:not(:disabled) {
background: var(--color-danger-bg);
}
.btn-danger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ---------- Бейджи статуса ---------- */
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 12px;
border-radius: var(--radius-full);
font: var(--text-caption);
white-space: nowrap;
}
.badge-free { background: var(--color-success-bg); color: var(--color-success); }
.badge-busy { background: var(--color-danger-bg); color: var(--color-danger); }
.badge-booked { background: var(--color-warning-bg); color: var(--color-warning); }
.badge-locked { background: var(--color-bg-alt); color: var(--color-ink-700); }
/* ---------- Индикатор загрузки (bootstrap-сессия, join и т.п.) ---------- */
.app-loader {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
color: var(--color-ink-500);
font: var(--text-body-lg);
background: var(--color-bg);
}
.spin {
animation: vidconf-spin 0.9s linear infinite;
}
@keyframes vidconf-spin {
to { transform: rotate(360deg); }
}
/* ---------- Тосты ---------- */
.toast-stack {
position: fixed;
right: var(--space-6);
bottom: var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-2);
z-index: 100;
}
.toast {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 18px;
border-radius: var(--radius-md);
font: var(--text-body);
font-weight: 600;
box-shadow: var(--shadow-lg);
max-width: 360px;
}
.toast-error { background: var(--color-danger-bg); color: var(--color-danger); }
.toast-success { background: var(--color-success-bg); color: var(--color-success); }
.toast-info { background: var(--color-surface); color: var(--color-ink-900); border: 1px solid var(--color-border); }
/* ---------- Ховер-карточка конференции (ConferenceHoverCard) ----------
* Новый элемент без готового макета в design/mockups/ — собран из уже
* утверждённых паттернов (карточка-поповер `--shadow-lg`, бейдж закреплённости
* с `Repeat` из §4.6). Рендерится порталом в document.body с `position: fixed`
* (см. ConferenceHoverCard.tsx) — `pointer-events: none`, чтобы не перекрывать
* клики по элементу под ней.
*/
.conf-hover-card {
pointer-events: none;
z-index: 250;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: var(--space-4);
font: var(--text-body);
color: var(--color-ink-900);
}
.conf-hover-card-hint { margin: 0; color: var(--color-ink-500); }
.conf-hover-card-title { margin: 0 0 var(--space-2); font: var(--text-h3); color: var(--color-ink-900); }
.conf-hover-card-row { display: flex; align-items: center; gap: 6px; margin: 0 0 6px; color: var(--color-ink-700); }
.conf-hover-card-row:last-of-type { margin-bottom: 0; }
.conf-hover-card-label { display: flex; align-items: center; gap: 5px; font: var(--text-caption); color: var(--color-ink-400); margin-right: 4px; }
.conf-hover-card-participants { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
.conf-hover-card-participants ul { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; max-height: 140px; overflow-y: auto; }
.conf-hover-card-participants li { display: flex; align-items: center; gap: 8px; font: var(--text-body); color: var(--color-ink-900); }
.conf-hover-card-tag { font: var(--text-caption); color: var(--color-ink-500); }
/* ---------- Модальные диалоги (светлая оболочка) ---------- */
/* Общая обёртка модалок оболочки (диалог быстрого просмотра вхождения
конференции, редактирование в «Моих конференциях» и т.п.) — центрированная
модалка (--shadow-lg) поверх затемнённого фона. */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(46, 52, 84, 0.4);
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-6);
z-index: 200;
}
.modal-panel {
background: var(--color-surface);
border-radius: var(--radius-2xl);
box-shadow: var(--shadow-lg);
padding: var(--space-6);
width: 100%;
max-width: 420px;
max-height: calc(100vh - var(--space-12));
overflow-y: auto;
}
.modal-panel.is-wide { max-width: 480px; }
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-5); }
.modal-head h2 { font: var(--text-h2); color: var(--color-ink-900); margin: 0; }
.modal-close {
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-ink-500);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.modal-close:hover { border-color: var(--color-border-strong); color: var(--color-ink-900); }
.modal-actions { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
.modal-actions .btn { flex: 1; justify-content: center; }
/* Опасное действие (удаление) — отдельно слева, Отмена/Сохранить — группой
справа, без растягивания на всю ширину (см. EditConferenceModal). */
.modal-actions.modal-actions--split { justify-content: space-between; }
.modal-actions--split > .btn-danger { flex: none; }
.modal-actions--split .modal-actions-group { display: flex; gap: var(--space-3); }
.modal-actions--split .row-actions { display: flex; align-items: center; gap: var(--space-3); }
/* Модалка вокруг ConferenceFormCard (`.create-card`, «Мои конференции» и
календарь) — сама карточка остаётся визуальной панелью
(фон/обводка/радиус/паддинг из calendar.css), здесь только гасим её
sticky-позиционирование из десктопного сайдбара и ограничиваем высоту
внутри модалки, чтобы скроллилась она сама, а не вся страница. */
.modal-form-wrap { width: 100%; max-width: 480px; }
.modal-form-wrap .create-card { position: static; max-height: calc(100vh - var(--space-12)); overflow-y: auto; }
/* Узкий экран: «Удалить» + «Отмена» + «Сохранить» в один ряд не помещаются
(в сумме шире содержимого модалки на телефоне) — переносим «Удалить» на
свою строку сверху. */
@media (max-width: 480px) {
.modal-actions--split { flex-wrap: wrap; }
.modal-actions--split > .btn-danger { flex: 1 1 100%; }
.modal-actions--split .modal-actions-group { flex: 1 1 100%; }
.modal-actions--split .row-actions { flex-wrap: wrap; }
}
/* ---------- Поля форм в модалках (общие для всех диалогов оболочки) ---------- */
.field { margin-bottom: var(--space-5); }
.field:last-child { margin-bottom: 0; }
.field label { display: flex; align-items: center; gap: 6px; font: var(--text-body); font-weight: 600; color: var(--color-ink-900); margin-bottom: var(--space-2); }
.field input, .field textarea, .field select {
width: 100%;
font: var(--text-body-lg);
color: var(--color-ink-900);
padding: 10px 14px;
border-radius: var(--radius-md);
border: 1px solid var(--color-border);
background: var(--color-surface);
font-family: var(--font-body);
}
.field input:focus, .field textarea:focus, .field select:focus {
outline: none;
border-color: var(--color-focus-ring);
box-shadow: 0 0 0 3px rgba(59, 77, 149, 0.12);
}
.field-hint { font: var(--text-body); color: var(--color-ink-500); margin: 4px 0 0; }
.error-banner {
display: flex;
gap: 10px;
align-items: flex-start;
background: var(--color-danger-bg);
border: 1px solid rgba(199, 54, 79, 0.3);
color: var(--color-danger);
border-radius: var(--radius-md);
padding: var(--space-4);
font: var(--text-body);
margin-bottom: var(--space-5);
}
.error-banner strong { display: block; margin-bottom: 2px; }
.error-banner svg { flex-shrink: 0; margin-top: 1px; }
/* =====================================================================
* Тёмная тема: точечные патчи (design/mockups/dark/README.md, «Точечные
* правки поверх скопированного CSS»). Все переменные --color-* / --shadow-*
* уже перекрашиваются сами через каскад tokens.css — здесь только правила,
* где использован литеральный hex/rgba (не var()) и поэтому не могли
* перекраситься автоматически. Оба блока (авто по ОС + явный выбор)
* продублированы намеренно, как в самом tokens.css.
* ===================================================================== */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .avatar { color: var(--color-accent-text); }
:root:not([data-theme]) .btn-primary:disabled { background: rgba(122, 122, 122, 0.3); color: var(--color-ink-300); }
:root:not([data-theme]) .modal-overlay { background: rgba(0, 0, 0, 0.6); }
:root:not([data-theme]) .field input:focus,
:root:not([data-theme]) .field textarea:focus,
:root:not([data-theme]) .field select:focus { box-shadow: 0 0 0 3px rgba(214, 168, 61, 0.18); }
:root:not([data-theme]) .error-banner { border-color: rgba(235, 147, 161, 0.3); }
}
:root[data-theme='dark'] .avatar { color: var(--color-accent-text); }
:root[data-theme='dark'] .btn-primary:disabled { background: rgba(122, 122, 122, 0.3); color: var(--color-ink-300); }
:root[data-theme='dark'] .modal-overlay { background: rgba(0, 0, 0, 0.6); }
:root[data-theme='dark'] .field input:focus,
:root[data-theme='dark'] .field textarea:focus,
:root[data-theme='dark'] .field select:focus { box-shadow: 0 0 0 3px rgba(214, 168, 61, 0.18); }
:root[data-theme='dark'] .error-banner { border-color: rgba(235, 147, 161, 0.3); }