From e13ddb9a8bd540114ef0cd27bb484674ee2daf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 15 Jan 2026 14:03:27 +0300 Subject: [PATCH] backup: SCSS before reorganization - create backup folder and plan --- .../abstracts/_css-variables.scss | 197 +++ .../abstracts/_index.scss | 8 + .../abstracts/_mixins.scss | 378 +++++ .../abstracts/_variables.scss | 223 +++ .../components/BUTTONS_README.md | 279 ++++ .../components/_badges-bem.scss | 232 +++ .../components/_badges-telegram-style.scss | 233 +++ .../components/_buttons.scss | 193 +++ .../components/_card-list.scss | 317 ++++ .../components/_cards.scss | 341 ++++ .../components/_comments.scss | 152 ++ .../components/_common.scss | 327 ++++ .../components/_department-controls.scss | 26 + .../components/_document-list.scss | 248 +++ .../components/_feed-specific.scss | 134 ++ .../components/_ios-components.scss | 546 +++++++ .../components/_ios-search.scss | 251 +++ .../components/_join-scroller.scss | 219 +++ .../components/_layout-spacing.scss | 42 + .../components/_list-view.scss | 148 ++ .../components/_logout-modal.scss | 182 +++ .../components/_modal-overrides.scss | 29 + .../components/_modals.scss | 40 + .../components/_navbar.scss | 262 +++ .../components/_notifications.scss | 471 ++++++ .../components/_page-header.scss | 477 ++++++ .../components/_post-form.scss | 20 + .../components/_recipientPicker.scss | 353 +++++ .../components/_request-detail.scss | 225 +++ .../components/_request-list.scss | 545 +++++++ .../components/_rightbar-calendar.scss | 331 ++++ .../components/_sidebar.scss | 198 +++ .../components/_spacing-utils.scss | 136 ++ .../components/_team-wheel.scss | 210 +++ .../components/chat/_chat-date-groups.scss | 183 +++ .../components/chat/_chat-detail.scss | 901 +++++++++++ .../components/chat/_chat-enhanced.scss | 298 ++++ .../components/chat/_chat-list-enhanced.scss | 320 ++++ .../chat/_chat-new-messages-indicator.scss | 149 ++ .../components/chat/_chat-polls.scss | 187 +++ .../chat/_message-context-menu.scss | 343 ++++ .../components/chat/_message-inputs.scss | 234 +++ .../components/chat/_message-reactions.scss | 335 ++++ .../components/chat/_message-selection.scss | 292 ++++ .../custom-bootstrap.scss | 201 +++ .../layout/_base-app.scss | 397 +++++ .../layout/_chat-layout.scss | 61 + backend/static/scss_backup_20260115/main.scss | 85 + docs/reports/SCSS_ARCHITECTURE_AUDIT.md | 461 ++++++ docs/reports/SCSS_REORGANIZATION_PLAN.md | 1412 +++++++++++++++++ 50 files changed, 13832 insertions(+) create mode 100644 backend/static/scss_backup_20260115/abstracts/_css-variables.scss create mode 100644 backend/static/scss_backup_20260115/abstracts/_index.scss create mode 100644 backend/static/scss_backup_20260115/abstracts/_mixins.scss create mode 100644 backend/static/scss_backup_20260115/abstracts/_variables.scss create mode 100644 backend/static/scss_backup_20260115/components/BUTTONS_README.md create mode 100644 backend/static/scss_backup_20260115/components/_badges-bem.scss create mode 100644 backend/static/scss_backup_20260115/components/_badges-telegram-style.scss create mode 100644 backend/static/scss_backup_20260115/components/_buttons.scss create mode 100644 backend/static/scss_backup_20260115/components/_card-list.scss create mode 100644 backend/static/scss_backup_20260115/components/_cards.scss create mode 100644 backend/static/scss_backup_20260115/components/_comments.scss create mode 100644 backend/static/scss_backup_20260115/components/_common.scss create mode 100644 backend/static/scss_backup_20260115/components/_department-controls.scss create mode 100644 backend/static/scss_backup_20260115/components/_document-list.scss create mode 100644 backend/static/scss_backup_20260115/components/_feed-specific.scss create mode 100644 backend/static/scss_backup_20260115/components/_ios-components.scss create mode 100644 backend/static/scss_backup_20260115/components/_ios-search.scss create mode 100644 backend/static/scss_backup_20260115/components/_join-scroller.scss create mode 100644 backend/static/scss_backup_20260115/components/_layout-spacing.scss create mode 100644 backend/static/scss_backup_20260115/components/_list-view.scss create mode 100644 backend/static/scss_backup_20260115/components/_logout-modal.scss create mode 100644 backend/static/scss_backup_20260115/components/_modal-overrides.scss create mode 100644 backend/static/scss_backup_20260115/components/_modals.scss create mode 100644 backend/static/scss_backup_20260115/components/_navbar.scss create mode 100644 backend/static/scss_backup_20260115/components/_notifications.scss create mode 100644 backend/static/scss_backup_20260115/components/_page-header.scss create mode 100644 backend/static/scss_backup_20260115/components/_post-form.scss create mode 100644 backend/static/scss_backup_20260115/components/_recipientPicker.scss create mode 100644 backend/static/scss_backup_20260115/components/_request-detail.scss create mode 100644 backend/static/scss_backup_20260115/components/_request-list.scss create mode 100644 backend/static/scss_backup_20260115/components/_rightbar-calendar.scss create mode 100644 backend/static/scss_backup_20260115/components/_sidebar.scss create mode 100644 backend/static/scss_backup_20260115/components/_spacing-utils.scss create mode 100644 backend/static/scss_backup_20260115/components/_team-wheel.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_chat-date-groups.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_chat-detail.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_chat-enhanced.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_chat-list-enhanced.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_chat-new-messages-indicator.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_chat-polls.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_message-context-menu.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_message-inputs.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_message-reactions.scss create mode 100644 backend/static/scss_backup_20260115/components/chat/_message-selection.scss create mode 100644 backend/static/scss_backup_20260115/custom-bootstrap.scss create mode 100644 backend/static/scss_backup_20260115/layout/_base-app.scss create mode 100644 backend/static/scss_backup_20260115/layout/_chat-layout.scss create mode 100644 backend/static/scss_backup_20260115/main.scss create mode 100644 docs/reports/SCSS_ARCHITECTURE_AUDIT.md create mode 100644 docs/reports/SCSS_REORGANIZATION_PLAN.md diff --git a/backend/static/scss_backup_20260115/abstracts/_css-variables.scss b/backend/static/scss_backup_20260115/abstracts/_css-variables.scss new file mode 100644 index 00000000..65159917 --- /dev/null +++ b/backend/static/scss_backup_20260115/abstracts/_css-variables.scss @@ -0,0 +1,197 @@ +// ============================================================================= +// CSS Custom Properties (CSS Variables) +// ============================================================================= +// CSS переменные, доступные в runtime через var(--variable-name) +// В отличие от SCSS переменных ($variable), эти работают в браузере + +:root { + // ========== Цветовая схема ========== + + // Основные цвета приложения + --app-primary: #0d6efd; + --app-success: #198754; + --app-danger: #dc3545; + --app-warning: #ffc107; + --app-info: #0dcaf0; + + // Фоновые цвета + --bg-light: #f8f9fa; + --bg-dark: #212529; + --bg-card: #ffffff; + + // Цвета текста + --text-primary: #212529; + --text-secondary: #6c757d; + --text-muted: #adb5bd; + + // ========== Размеры и отступы ========== + + // Высота компонентов + --navbar-h: 60px; + --sidebar-w: 200px; + --rightbar-w: 350px; + + // Радиусы скругления + --radius-xs: 4px; + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 18px; + --radius-2xl: 24px; + --radius-full: 50%; + + // Радиусы компонентов + --feed-radius: var(--radius-xl); + --card-radius: var(--radius-lg); + --button-radius: var(--radius-md); + --input-radius: var(--radius-md); + --badge-radius: 999px; + + // ========== Унифицированная система отступов (8px grid) ========== + + // Базовая шкала отступов + --space-0: 0; + --space-1: 0.25rem; // 4px + --space-2: 0.5rem; // 8px + --space-3: 0.75rem; // 12px + --space-4: 1rem; // 16px + --space-5: 1.5rem; // 24px + + // Семантические названия (для лучшей читаемости) + --space-xs: var(--space-1); + --space-sm: var(--space-2); + --space-md: var(--space-3); + --space-lg: var(--space-4); + --space-xl: var(--space-5); + + // Специфичные отступы для компонентов + --gap-component: var(--space-3); + --gap-section: var(--space-4); + + // Padding карточек и контейнеров + --card-padding: var(--space-2); + --card-padding-sm: var(--space-1); + --card-padding-lg: var(--space-3); + + // Margin между элементами + --margin-section: var(--space-3); + --margin-block: var(--space-4); + + // Layout отступы (app-layout grid) + --layout-gap: var(--space-2); + --layout-padding-y: var(--space-3); + --layout-padding-x: 0; + + // Старые переменные (deprecated, для обратной совместимости) + --spacing-xs: var(--space-1); + --spacing-sm: var(--space-2); + --spacing-md: var(--space-3); + --spacing-lg: var(--space-4); + --spacing-xl: var(--space-5); + + // ========== Тени ========== + + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.12); + --shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.16); + + // Тени компонентов + --feed-shadow: var(--shadow-md); + --feed-shadow-hover: var(--shadow-lg); + --card-shadow: var(--shadow-sm); + --dropdown-shadow: var(--shadow-lg); + --modal-shadow: var(--shadow-xl); + + // ========== Компоненты Feed ========== + + --feed-gap: 0.75rem; + + // ========== iOS Компоненты ========== + + --ios-overlay-bg: rgba(0, 0, 0, 0.4); + --ios-sheet-radius: 24px 24px 0 0; + --ios-grip-w: 36px; + --ios-grip-h: 5px; + --ios-search-radius: var(--radius-md); + + // ========== Аватары ========== + + --avatar-xs: 28px; + --avatar-sm: 36px; + --avatar-md: 44px; + --avatar-lg: 64px; + --avatar-xl: 96px; + + // ========== Календарь ========== + + --calendar-day-size: 1.6rem; + --calendar-event-radius: 6px; + --calendar-wrap-radius: 10px; + + // ========== Переходы и анимации ========== + + --transition-fast: 0.15s ease; + --transition-base: 0.2s ease; + --transition-slow: 0.3s ease; + + // ========== Z-индексы ========== + + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal-backdrop: 1040; + --z-modal: 1050; + --z-popover: 1060; + --z-tooltip: 1070; + + // ========== Типографика ========== + + --font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-family-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + + --font-size-xs: 0.75rem; + --font-size-sm: 0.875rem; + --font-size-base: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + + --font-weight-light: 300; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + + // ========== Границы ========== + + --border-width: 1px; + --border-color: #dee2e6; + --border-radius: var(--radius-md); +} + +// ========== Вспомогательные классы с переменными ========== + +.shadow-sm { box-shadow: var(--shadow-sm); } +.shadow-md { box-shadow: var(--shadow-md); } +.shadow-lg { box-shadow: var(--shadow-lg); } +.shadow-xl { box-shadow: var(--shadow-xl); } + +.radius-sm { border-radius: var(--radius-sm); } +.radius-md { border-radius: var(--radius-md); } +.radius-lg { border-radius: var(--radius-lg); } +.radius-xl { border-radius: var(--radius-xl); } +.radius-full { border-radius: var(--radius-full); } + +.transition-fast { transition: all var(--transition-fast); } +.transition-base { transition: all var(--transition-base); } +.transition-slow { transition: all var(--transition-slow); } + +// ========== Адаптивные переменные ========== + +// На мобильных navbar выше (90px вместо 60px) +@media (max-width: 767.98px) { + :root { + --navbar-h: 90px; + } +} diff --git a/backend/static/scss_backup_20260115/abstracts/_index.scss b/backend/static/scss_backup_20260115/abstracts/_index.scss new file mode 100644 index 00000000..3360c5d0 --- /dev/null +++ b/backend/static/scss_backup_20260115/abstracts/_index.scss @@ -0,0 +1,8 @@ +// ============================================================================= +// Index - Centralized Imports +// ============================================================================= +// Единая точка импорта всех abstracts компонентов + +@import "variables"; +@import "css-variables"; +@import "mixins"; diff --git a/backend/static/scss_backup_20260115/abstracts/_mixins.scss b/backend/static/scss_backup_20260115/abstracts/_mixins.scss new file mode 100644 index 00000000..81f229bd --- /dev/null +++ b/backend/static/scss_backup_20260115/abstracts/_mixins.scss @@ -0,0 +1,378 @@ +// ============================================================================= +// Mixins - Reusable Style Patterns +// ============================================================================= +// Переиспользуемые паттерны для DRY (Don't Repeat Yourself) подхода + +// ============================================================================= +// 1. GLASSMORPHISM EFFECTS +// ============================================================================= + +/// Применяет эффект glassmorphism (размытое стекло) +/// @param {string} $blur - Уровень размытия (по умолчанию $blur-md) +/// @param {color} $bg - Цвет фона с прозрачностью +/// @param {color} $border - Цвет границы (опционально) +@mixin glassmorphism($blur: $blur-md, $bg: $glass-light-bg, $border: null) { + backdrop-filter: $blur saturate(180%); + -webkit-backdrop-filter: $blur saturate(180%); + background: $bg; + + @if $border { + border: 1px solid $border; + } +} + +/// Glassmorphism для светлой темы +@mixin glassmorphism-light() { + @include glassmorphism($blur-md, $glass-light-bg, $glass-light-border); +} + +/// Glassmorphism для тёмной темы +@mixin glassmorphism-dark() { + @include glassmorphism($blur-md, $glass-dark-bg, $glass-dark-border); +} + +/// Glassmorphism для карточек +@mixin glassmorphism-card() { + @include glassmorphism($blur-md, $card-glass-bg, $card-glass-border); + box-shadow: $card-glass-shadow; +} + +// ============================================================================= +// 2. SHADOWS +// ============================================================================= + +/// Применяет тень с возможностью hover эффекта +/// @param {string} $shadow - Базовая тень +/// @param {string} $shadow-hover - Тень при наведении (опционально) +@mixin shadow($shadow: $shadow-md, $shadow-hover: null) { + box-shadow: $shadow; + + @if $shadow-hover { + transition: box-shadow $transition-base; + + &:hover { + box-shadow: $shadow-hover; + } + } +} + +/// Удаляет все тени +@mixin no-shadow() { + box-shadow: none !important; +} + +// ============================================================================= +// 3. BORDER RADIUS +// ============================================================================= + +/// Применяет border-radius с единым значением +/// @param {string} $radius - Размер радиуса +@mixin radius($radius: $radius-lg) { + border-radius: $radius; +} + +/// Скругляет только верхние углы +@mixin radius-top($radius: $radius-lg) { + border-top-left-radius: $radius; + border-top-right-radius: $radius; +} + +/// Скругляет только нижние углы +@mixin radius-bottom($radius: $radius-lg) { + border-bottom-left-radius: $radius; + border-bottom-right-radius: $radius; +} + +/// Создаёт идеальный круг +@mixin circle() { + border-radius: 50%; +} + +/// Создаёт pill-образную форму (капсула) +@mixin pill() { + border-radius: $radius-pill; +} + +// ============================================================================= +// 4. FLEXBOX UTILITIES +// ============================================================================= + +/// Центрирует содержимое по обеим осям +@mixin flex-center() { + display: flex; + align-items: center; + justify-content: center; +} + +/// Flexbox с вертикальным центрированием +@mixin flex-align-center() { + display: flex; + align-items: center; +} + +/// Flexbox с горизонтальным центрированием +@mixin flex-justify-center() { + display: flex; + justify-content: center; +} + +/// Flexbox со space-between +@mixin flex-between() { + display: flex; + align-items: center; + justify-content: space-between; +} + +// ============================================================================= +// 5. POSITION UTILITIES +// ============================================================================= + +/// Абсолютное позиционирование по центру +@mixin absolute-center() { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +/// Абсолютное позиционирование с полным покрытием +@mixin absolute-cover() { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +/// Sticky positioning с поддержкой navbar offset +/// @param {string} $top - Отступ сверху +@mixin sticky($top: 0) { + position: sticky; + top: $top; + z-index: $z-sticky; +} + +// ============================================================================= +// 6. TRANSITIONS +// ============================================================================= + +/// Применяет transition к указанным свойствам +/// @param {list} $properties - Список свойств для анимации +/// @param {string} $duration - Длительность (по умолчанию $transition-base) +@mixin transition($properties, $duration: $transition-base) { + transition-property: $properties; + transition-duration: $duration; + transition-timing-function: ease; +} + +/// Плавная анимация для всех свойств +@mixin transition-all($duration: $transition-base) { + transition: all $duration; +} + +// ============================================================================= +// 7. SCROLLBAR STYLING +// ============================================================================= + +/// Кастомный scrollbar +/// @param {color} $track-bg - Цвет трека +/// @param {color} $thumb-bg - Цвет ползунка +/// @param {string} $width - Ширина scrollbar +@mixin custom-scrollbar($track-bg: rgba(0, 0, 0, 0.05), $thumb-bg: rgba(0, 0, 0, 0.3), $width: 6px) { + scrollbar-width: thin; + scrollbar-color: $thumb-bg $track-bg; + + &::-webkit-scrollbar { + width: $width; + height: $width; + } + + &::-webkit-scrollbar-track { + background: $track-bg; + border-radius: $radius-sm; + } + + &::-webkit-scrollbar-thumb { + background: $thumb-bg; + border-radius: $radius-sm; + + &:hover { + background: rgba($thumb-bg, 0.8); + } + } +} + +// ============================================================================= +// 8. TEXT UTILITIES +// ============================================================================= + +/// Обрезает текст с многоточием +@mixin text-truncate() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/// Ограничивает текст указанным количеством строк +/// @param {number} $lines - Количество строк +@mixin text-clamp($lines: 2) { + display: -webkit-box; + -webkit-line-clamp: $lines; + -webkit-box-orient: vertical; + overflow: hidden; +} + +// ============================================================================= +// 9. HOVER EFFECTS +// ============================================================================= + +/// Стандартный hover эффект с opacity +/// @param {number} $opacity - Прозрачность при наведении +@mixin hover-opacity($opacity: 0.8) { + transition: opacity $transition-fast; + cursor: pointer; + + &:hover { + opacity: $opacity; + } +} + +/// Hover с подсветкой фона +/// @param {color} $bg - Цвет фона при наведении +@mixin hover-bg($bg: $gray-50) { + transition: background-color $transition-fast; + cursor: pointer; + + &:hover { + background-color: $bg; + } +} + +/// Hover с transform scale +/// @param {number} $scale - Масштаб при наведении +@mixin hover-scale($scale: 1.05) { + transition: transform $transition-base $ease-smooth; + cursor: pointer; + + &:hover { + transform: scale($scale); + } +} + +// ============================================================================= +// 10. BADGE UTILITIES +// ============================================================================= + +/// Создаёт базовый Telegram-style badge +/// @param {string} $height - Высота badge +/// @param {string} $radius - Border radius +@mixin badge-base($height: $badge-height-base, $radius: $badge-radius) { + @include flex-center(); + height: $height; + min-width: $height; + border-radius: $radius; + font-size: $badge-font-size; + font-weight: $badge-font-weight; + line-height: 1; +} + +/// Badge с тенью +@mixin badge-shadow() { + box-shadow: $badge-shadow; + + &:hover { + box-shadow: $badge-shadow-hover; + } +} + +// ============================================================================= +// 11. RESPONSIVE UTILITIES +// ============================================================================= + +/// Media query для mobile устройств +@mixin mobile() { + @media (max-width: 767.98px) { + @content; + } +} + +/// Media query для tablet устройств +@mixin tablet() { + @media (min-width: 768px) and (max-width: 991.98px) { + @content; + } +} + +/// Media query для desktop устройств +@mixin desktop() { + @media (min-width: 992px) { + @content; + } +} + +/// Media query для больших экранов +@mixin large-desktop() { + @media (min-width: 1200px) { + @content; + } +} + +// ============================================================================= +// 12. DARK MODE UTILITIES +// ============================================================================= + +/// Применяет стили для тёмной темы +@mixin dark-mode() { + [data-bs-theme="dark"] & { + @content; + } +} + +/// Применяет стили для светлой темы +@mixin light-mode() { + [data-bs-theme="light"] & { + @content; + } +} + +// ============================================================================= +// 13. ANIMATION UTILITIES +// ============================================================================= + +/// Fade-in анимация +@mixin fade-in($duration: $transition-base) { + animation: fadeIn $duration ease-in; +} + +/// Slide-down анимация +@mixin slide-down($duration: $transition-base) { + animation: slideDown $duration $ease-smooth; +} + +/// Pulse анимация +@mixin pulse($duration: 2s) { + animation: pulse $duration infinite; +} + +// ============================================================================= +// 14. FOCUS STATES +// ============================================================================= + +/// Bootstrap-style focus ring +/// @param {color} $color - Цвет focus ring +@mixin focus-ring($color: var(--bs-primary-rgb)) { + &:focus, + &:focus-visible { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba($color, 0.25); + } +} + +/// Убирает focus outline +@mixin no-focus() { + &:focus, + &:focus-visible { + outline: none; + box-shadow: none; + } +} diff --git a/backend/static/scss_backup_20260115/abstracts/_variables.scss b/backend/static/scss_backup_20260115/abstracts/_variables.scss new file mode 100644 index 00000000..962aa359 --- /dev/null +++ b/backend/static/scss_backup_20260115/abstracts/_variables.scss @@ -0,0 +1,223 @@ +// ============================================================================= +// Design Tokens - Centralized Variables +// ============================================================================= +// Централизованная система переменных для всего проекта +// Все компоненты должны использовать эти токены вместо hardcoded значений + +// ============================================================================= +// 1. ЦВЕТОВАЯ СИСТЕМА +// ============================================================================= + +// Telegram colors +$telegram-blue: #0088cc; +$telegram-mention-bg: #0088cc; + +// Gradient colors +$gradient-purple-start: #667eea; +$gradient-purple-end: #764ba2; +$gradient-pink-start: #f093fb; +$gradient-pink-end: #f5576c; + +// Base colors +$color-white: #fff; +$color-black: #000; + +// Gray scale +$gray-50: #f8f9fa; +$gray-100: #f0f0f0; +$gray-200: #e3e3e3; +$gray-300: #dee2e6; +$gray-400: #ced4da; +$gray-500: #adb5bd; +$gray-600: #6c757d; +$gray-700: #495057; +$gray-800: #343a40; +$gray-900: #212529; + +// Semantic colors +$color-danger: #ef4444; +$color-success-light: #e3f2fd; +$color-warning-light: #fffbeb; + +// ============================================================================= +// 2. SPACING SYSTEM (уже определена в custom-bootstrap.scss) +// ============================================================================= +// Используем Bootstrap переменные $spacers +// 0: 0, 1: 4px, 2: 8px, 3: 12px, 4: 16px, 5: 24px + +// ============================================================================= +// 3. BORDER RADIUS TOKENS +// ============================================================================= + +$radius-xs: 4px; // extra small +$radius-sm: 6px; // small +$radius-md: 10px; // medium (most common) +$radius-lg: 12px; // large +$radius-xl: 14px; // extra large +$radius-2xl: 16px; // 2x large +$radius-3xl: 18px; // 3x large +$radius-4xl: 20px; // 4x large +$radius-pill: 999px; // perfect pill shape + +// Badge specific (perfect circles) +$badge-radius: 10px; +$badge-radius-sm: 9px; +$badge-radius-lg: 12px; +$badge-radius-xl: 14px; + +// ============================================================================= +// 4. SHADOW TOKENS +// ============================================================================= + +$shadow-none: none; +$shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.06); +$shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); +$shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1); +$shadow-lg: 0 2px 8px rgba(0, 0, 0, 0.15); +$shadow-xl: 0 4px 12px rgba(0, 0, 0, 0.15); +$shadow-2xl: 0 4px 16px rgba(0, 0, 0, 0.2); +$shadow-3xl: 0 6px 20px rgba(0, 0, 0, 0.3); + +// Badge specific shadows +$badge-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); +$badge-shadow-hover: 0 3px 6px rgba(0, 0, 0, 0.2); +$badge-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.3); + +// Context menu shadows +$context-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + +// ============================================================================= +// 5. BLUR TOKENS (для backdrop-filter) +// ============================================================================= + +$blur-none: blur(0); +$blur-sm: blur(8px); +$blur-md: blur(10px); +$blur-lg: blur(12px); +$blur-xl: blur(16px); + +// ============================================================================= +// 6. OPACITY TOKENS +// ============================================================================= + +$opacity-glass-light: 0.1; +$opacity-glass-medium: 0.15; +$opacity-glass-high: 0.2; + +$opacity-overlay-light: 0.05; +$opacity-overlay-medium: 0.1; +$opacity-overlay-dark: 0.3; + +$opacity-border-light: 0.08; +$opacity-border-medium: 0.1; +$opacity-border-dark: 0.25; + +// ============================================================================= +// 7. TRANSITION TOKENS +// ============================================================================= + +$transition-fast: 0.15s ease; +$transition-base: 0.2s ease; +$transition-medium: 0.3s ease; +$transition-slow: 0.4s ease; + +// Специальные easing функции +$ease-bounce: cubic-bezier(0.175, 0.885, 0.32, 1.275); +$ease-smooth: cubic-bezier(0.4, 0, 0.2, 1); + +// Badge specific +$badge-transition: transform 0.2s $ease-bounce; + +// ============================================================================= +// 8. Z-INDEX TOKENS +// ============================================================================= + +$z-base: 1; +$z-dropdown: 1000; +$z-sticky: 1020; +$z-fixed: 1030; +$z-modal-backdrop: 1040; +$z-modal: 1050; +$z-popover: 1060; +$z-tooltip: 1070; +$z-notification: 1080; + +// ============================================================================= +// 9. SIZE TOKENS (avatars, badges, icons) +// ============================================================================= + +// Badge heights +$badge-height-sm: 18px; +$badge-height-base: 20px; +$badge-height-lg: 24px; +$badge-height-xl: 28px; + +// Badge padding +$badge-padding-x: 7px; +$badge-padding-y: 2px; + +// Avatar sizes +$avatar-xs: 24px; +$avatar-sm: 32px; +$avatar-md: 40px; +$avatar-lg: 44px; +$avatar-xl: 48px; +$avatar-2xl: 56px; + +// Icon button sizes +$btn-icon-sm: 32px; +$btn-icon-base: 36px; +$btn-icon-lg: 44px; + +// ============================================================================= +// 10. GLASSMORPHISM PRESETS +// ============================================================================= + +// Light theme glassmorphism +$glass-light-bg: rgba(255, 255, 255, 0.1); +$glass-light-border: rgba(0, 0, 0, 0.08); + +// Dark theme glassmorphism +$glass-dark-bg: rgba(52, 58, 64, 0.95); +$glass-dark-border: rgba(255, 255, 255, 0.1); + +// Card glassmorphism +$card-glass-bg: rgba(255, 255, 255, 0.05); +$card-glass-border: rgba(255, 255, 255, 0.1); +$card-glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); + +// ============================================================================= +// 11. TYPOGRAPHY TOKENS (дополнительные) +// ============================================================================= + +$font-weight-normal: 400; +$font-weight-medium: 500; +$font-weight-semibold: 600; +$font-weight-bold: 700; + +$font-size-xs: 11px; +$font-size-sm: 12px; +$font-size-base: 14px; +$font-size-md: 16px; +$font-size-lg: 18px; + +// Badge specific +$badge-font-size: 11px; +$badge-font-weight: 600; + +// ============================================================================= +// 12. LAYOUT TOKENS +// ============================================================================= + +// Navbar +$navbar-height: 56px; +$navbar-mobile-height: 90px; +$bottom-navbar-height: 60px; + +// Sidebar +$sidebar-width: 240px; +$sidebar-collapsed-width: 60px; + +// Chat specific +$chat-composer-height: auto; +$chat-message-max-width: 70%; diff --git a/backend/static/scss_backup_20260115/components/BUTTONS_README.md b/backend/static/scss_backup_20260115/components/BUTTONS_README.md new file mode 100644 index 00000000..92e6cf3d --- /dev/null +++ b/backend/static/scss_backup_20260115/components/BUTTONS_README.md @@ -0,0 +1,279 @@ +# Документация: Компонент Buttons + +## Обзор + +Расширенная система кнопок, дополняющая стандартные кнопки Bootstrap дополнительными вариантами и утилитами. + +## Классы кнопок + +### 1. Круглые кнопки (`.btn-circle`) + +Идеально круглые кнопки для иконок. Автоматически становятся квадратными с `border-radius: 50%`. + +#### Базовое использование + +```html + + + +``` + +#### Размеры + +```html + + + + + + + + +``` + +Или используйте специальные классы: +```html + + + +``` + +#### Состояния + +Toggle-кнопки автоматически вращаются при раскрытии: + +```html + + +``` + +### 2. Кнопки с иконками (`.btn-icon`) + +Кнопки с иконкой и текстом. На мобильных устройствах текст скрывается. + +```html + + + + + Новый документ + +``` + +**Поведение:** +- Десктоп: иконка + текст +- Мобильные (< 768px): только иконка (круглая кнопка) + +### 3. Кнопки-призраки (`.btn-ghost`) + +Прозрачные кнопки без фона, появляется фон при наведении. + +```html + + + +``` + +### 4. Pill-кнопки (`.btn-pill`) + +Максимально скругленные кнопки (`border-radius: 999px`). + +```html + + + + Узнать больше + +``` + +## Группы кнопок + +### Группа круглых кнопок + +```html +
+ + + +
+``` + +## Примеры использования + +### Пример 1: Шапка страницы с фильтрами + +```html +
+

Сотрудники

+ +
+ + + + + + + + +
+
+``` + +### Пример 2: Карточка с действиями + +```html +
+
+
Название документа
+

Описание...

+ +
+ + + +
+
+
+``` + +### Пример 3: Навигация с разными размерами + +```html +
+ + + + + + + + +
+``` + +## Комбинирование с Bootstrap + +Все новые классы полностью совместимы со стандартными классами Bootstrap: + +```html + + + + + + + + + + + + + +``` + +## Миграция со старого кода + +### Было (специфичные стили): + +```html + +``` + +```css +.page-filter-toggle { + width: 36px !important; + height: 36px !important; + border-radius: 50% !important; + /* ... еще 20 строк CSS ... */ +} +``` + +### Стало (универсальный класс): + +```html + +``` + +```css +/* Никаких дополнительных стилей не нужно! */ +``` + +## CSS переменные + +Компонент использует стандартные переменные Bootstrap: +- `--bs-primary` - основной цвет +- `--bs-body-color` - цвет текста +- `--bs-border-color` - цвет границ +- `--bs-border-radius` - скругление углов + +## Поддержка темной темы + +Все кнопки автоматически адаптируются под темную тему через `prefers-color-scheme: dark`. + +## Доступность + +- Все кнопки имеют правильные `aria-label` атрибуты +- Иконки используют `line-height: 1` для правильного центрирования +- Фокус-стейты наследуются от Bootstrap +- Поддержка клавиатурной навигации + +## Производительность + +- Минимальное использование `!important` (только где абсолютно необходимо) +- CSS переменные для легкой кастомизации +- Оптимизированные селекторы +- Не влияет на другие компоненты diff --git a/backend/static/scss_backup_20260115/components/_badges-bem.scss b/backend/static/scss_backup_20260115/components/_badges-bem.scss new file mode 100644 index 00000000..969c4eda --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_badges-bem.scss @@ -0,0 +1,232 @@ +// ============================================================================= +// Badges Component +// ============================================================================= +// Notification badges with Telegram-style design +// Uses design tokens from abstracts/_variables.scss + +// ============================================================================= +// 1. BASE COMPONENT +// ============================================================================= + +.badge { + @include badge-base($badge-height-base, $badge-radius); + @include badge-shadow(); + color: $color-white; + background: var(--bs-danger); + transition: $badge-transition; + user-select: none; + padding: 0 6px; + + // Auto-hide when empty + &:empty { + display: none; + } + + // Hide when count is 0 + &[data-count="0"] { + display: none; + } +} + +// ============================================================================= +// 2. SIZE MODIFIERS +// ============================================================================= + +.badge--sm { + @include badge-base($badge-height-sm, $badge-radius-sm); + font-size: 10px; + padding: 0 5px; +} + +.badge--lg { + @include badge-base($badge-height-lg, $badge-radius-lg); + font-size: 12px; + padding: 0 7px; +} + +.badge--xl { + @include badge-base($badge-height-xl, $badge-radius-xl); + font-size: 13px; + padding: 0 8px; +} + +// ============================================================================= +// 3. COLOR MODIFIERS +// ============================================================================= + +.badge--primary { + background: var(--bs-primary); +} + +.badge--secondary { + background: var(--bs-secondary); + opacity: 0.8; +} + +.badge--success { + background: var(--bs-success); +} + +.badge--warning { + background: var(--bs-warning); + color: var(--bs-dark); +} + +.badge--info { + background: var(--bs-info); +} + +.badge--mention { + background: $telegram-blue; +} + +.badge--muted { + background: var(--bs-secondary); + opacity: 0.6; + color: var(--bs-secondary-color); +} + +// ============================================================================= +// 4. STYLE MODIFIERS +// ============================================================================= + +.badge--outlined { + background: transparent; + border: 2px solid currentColor; + color: var(--bs-danger); +} + +.badge--dot { + width: 8px; + height: 8px; + min-width: 8px; + padding: 0; + @include radius(50%); +} + +.badge--inline { + display: inline-flex; + vertical-align: middle; + margin-left: 4px; +} + +// Premium badge with gradient +.badge--premium { + background: linear-gradient(135deg, $gradient-purple-start 0%, $gradient-purple-end 100%); +} + +.badge--star { + background: linear-gradient(135deg, $gradient-pink-start 0%, $gradient-pink-end 100%); +} + +// ============================================================================= +// 5. CONTEXT MODIFIERS +// ============================================================================= + +// Badge on avatar +.badge--avatar { + position: absolute; + top: 0; + right: 0; + transform: translate(25%, -25%); + z-index: $z-base; +} + +// Badge on button +.badge--button { + position: absolute; + top: -6px; + right: -6px; +} + +// Interactive badge (clickable) +.badge--interactive { + cursor: pointer; + @include transition-all($transition-fast); + + &:hover { + transform: scale(1.1); + box-shadow: $badge-shadow-hover; + } + + &:active { + transform: scale(0.95); + } +} + +// ============================================================================= +// 6. STATES +// ============================================================================= + +.badge.is-hidden { + display: none; +} + +.badge.is-animating-in { + animation: badge-scale-in 0.2s $ease-bounce; +} + +.badge.is-pulsing { + animation: badge-pulse 1.5s infinite; +} + +.badge.is-animating-out { + animation: badge-scale-out 0.2s $ease-bounce forwards; +} + +// ============================================================================= +// 7. DARK MODE +// ============================================================================= + +[data-bs-theme="dark"] { + .badge { + box-shadow: $badge-shadow-dark; + } + + .badge--outlined { + border-width: 1px; + } + + .badge--avatar { + height: calc($badge-height-base - 2px); + min-width: calc($badge-height-base - 2px); + } +} + +// ============================================================================= +// 8. ANIMATIONS +// ============================================================================= + +@keyframes badge-scale-in { + from { + transform: scale(0); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +@keyframes badge-pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.15); + } + 100% { + transform: scale(1); + } +} + +@keyframes badge-scale-out { + from { + transform: scale(1); + opacity: 1; + } + to { + transform: scale(0); + opacity: 0; + } +} diff --git a/backend/static/scss_backup_20260115/components/_badges-telegram-style.scss b/backend/static/scss_backup_20260115/components/_badges-telegram-style.scss new file mode 100644 index 00000000..69431de2 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_badges-telegram-style.scss @@ -0,0 +1,233 @@ +// ============================================================================= +// Telegram-Style Badges +// ============================================================================= +// Based on Telegram Web K design system (morethanwords/tweb) +// Perfect circles, smooth animations, professional appearance +// REFACTORED: использует design tokens из abstracts/_variables.scss + +// ============================================================================= +// Base Badge Class +// ============================================================================= +.badge-telegram { + @include badge-base($badge-height-base, $badge-radius); + color: $color-white; + background: var(--bs-danger); + @include badge-shadow(); + transition: $badge-transition; + user-select: none; + padding: 0 $badge-padding-x; + + // Auto-hide when empty + &:empty { + display: none; + } + + // Hide when count is 0 + &[data-count="0"] { + display: none; + } +} + +// ============================================================================= +// Size Variants +// ============================================================================= +.badge-telegram-sm { + @include badge-base($badge-height-sm, $badge-radius-sm); + font-size: 10px; + padding: 0 5px; +} + +.badge-telegram-lg { + @include badge-base($badge-height-lg, $badge-radius-lg); + font-size: 12px; + padding: 0 7px; +} + +.badge-telegram-xl { + @include badge-base($badge-height-xl, $badge-radius-xl); + font-size: 13px; + padding: 0 8px; +} + +// ============================================================================= +// Color Variants +// ============================================================================= +.badge-telegram-primary { + background: var(--bs-primary); +} + +.badge-telegram-muted { + background: var(--bs-secondary); + opacity: 0.8; +} + +.badge-telegram-success { + background: var(--bs-success); +} + +.badge-telegram-warning { + background: var(--bs-warning); + color: var(--bs-dark); +} + +.badge-telegram-info { + background: var(--bs-info); +} + +.badge-telegram-mention { + background: $telegram-blue; +} + +// ============================================================================= +// Animations +// ============================================================================= +@keyframes badge-scale-in { + from { + transform: scale(0); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +@keyframes badge-pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.15); + } + 100% { + transform: scale(1); + } +} + +@keyframes badge-scale-out { + from { + transform: scale(1); + opacity: 1; + } + to { + transform: scale(0); + opacity: 0; + } +} + +// Animation Classes +.badge-telegram.animate-in { + animation: badge-scale-in 0.2s $ease-bounce; +} + +.badge-telegram.pulse { + animation: badge-pulse 0.6s $badge-transition; +} + +.badge-telegram.animate-out { + animation: badge-scale-out 0.2s $badge-transition; +} + +// ============================================================================= +// Positioning Variants +// ============================================================================= +// On avatar +.badge-telegram-avatar { + position: absolute; + bottom: -2px; + right: -2px; + border: 2px solid white; +} + +// On button/icon +.badge-telegram-button { + position: absolute; + top: -6px; + right: -10px; + z-index: 1; +} + +// Inline with text +.badge-telegram-inline { + position: relative; + top: -1px; + margin-left: 4px; +} + +// ============================================================================= +// Special Effects +// ============================================================================= +// Dot badge (no number) +.badge-telegram-dot { + width: 8px; + height: 8px; + min-width: 8px; + padding: 0; + border-radius: 50%; +} + +// Interactive badge (hoverable) +.badge-telegram-interactive { + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + transform: scale(1.1); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2); + } + + &:active { + transform: scale(0.95); + } +} + +// Premium badge (gradient) +.badge-telegram-premium { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +// Star badge (for favorites) +.badge-telegram-star { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +// ============================================================================= +// Responsive Design +// ============================================================================= +@media (max-width: 576px) { + .badge-telegram { + height: calc($badge-height-base - 2px); + min-width: calc($badge-height-base - 2px); + font-size: calc($badge-font-size - 1px); + padding: 0 calc($badge-padding-x - 1px); + } +} + +// ============================================================================= +// Dark Theme Support +// ============================================================================= +@media (prefers-color-scheme: dark) { + .badge-telegram { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + } + + .badge-telegram-avatar { + border-color: var(--bs-dark); + } +} + +// ============================================================================= +// Utilities +// ============================================================================= +// Hide badge class (for JavaScript manipulation) +.badge-telegram-hidden { + display: none !important; +} + +// Badge with border +.badge-telegram-outlined { + background: transparent; + color: var(--bs-danger); + border: 2px solid var(--bs-danger); + box-shadow: none; +} diff --git a/backend/static/scss_backup_20260115/components/_buttons.scss b/backend/static/scss_backup_20260115/components/_buttons.scss new file mode 100644 index 00000000..c3df7b91 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_buttons.scss @@ -0,0 +1,193 @@ +// ============================================================================= +// Custom Button Styles (SCSS) +// ============================================================================= +// Расширение стандартных кнопок Bootstrap с дополнительными вариантами: +// - .btn-circle - круглая кнопка +// - .btn-icon - кнопка с иконкой и текстом +// - .btn-ghost - кнопка-призрак +// - .btn-pill - максимально скругленная кнопка + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +// Размеры круглых кнопок (используем значения, кратные 4px) +$btn-circle-size-sm: 32px; +$btn-circle-size-md: 36px; +$btn-circle-size-lg: 44px; + +// Размеры шрифтов +$btn-circle-font-sm: 0.875rem; +$btn-circle-font-md: 1rem; +$btn-circle-font-lg: 1.25rem; + +// Отступы +$btn-icon-gap: var(--space-2); // 8px +$btn-circle-mobile-size: 36px; +$btn-circle-mobile-padding: var(--space-2); // 8px + +// Переходы +$btn-transition: all 0.2s ease; +$btn-toggle-transition: transform 0.3s ease; + +// ============================================================================= +// КРУГЛЫЕ КНОПКИ +// ============================================================================= + +// Миксин для размеров круглых кнопок (DRY) +@mixin btn-circle-size($size, $font-size) { + width: $size !important; + height: $size !important; + min-width: $size !important; + max-width: $size !important; + min-height: $size !important; + max-height: $size !important; + font-size: $font-size; +} + +// Базовый класс для круглых кнопок +.btn-circle { + border-radius: 50% !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center; + justify-content: center; + flex-shrink: 0; + aspect-ratio: 1 / 1; + overflow: hidden; + line-height: 1; + + // Иконка внутри круглой кнопки + i { + line-height: 1; + display: inline-block; + } +} + +// Размеры круглых кнопок +.btn-circle.btn-sm, +.btn-circle-sm { + @include btn-circle-size($btn-circle-size-sm, $btn-circle-font-sm); +} + +.btn-circle:not(.btn-sm):not(.btn-lg), +.btn-circle-md { + @include btn-circle-size($btn-circle-size-md, $btn-circle-font-md); +} + +.btn-circle.btn-lg, +.btn-circle-lg { + @include btn-circle-size($btn-circle-size-lg, $btn-circle-font-lg); +} + +// ============================================================================= +// КНОПКИ С ИКОНКАМИ +// ============================================================================= + +// Кнопка с иконкой и текстом +.btn-icon { + display: inline-flex; + align-items: center; + gap: $btn-icon-gap; + + i { + line-height: 1; + } +} + +// Адаптив: только иконка на мобильных +@media (max-width: 767.98px) { + .btn-icon { + .btn-text { + display: none; + } + + // Если это не круглая кнопка, делаем её круглой на мобильных + &:not(.btn-circle) { + border-radius: 50% !important; + padding: $btn-circle-mobile-padding !important; + width: $btn-circle-mobile-size; + height: $btn-circle-mobile-size; + justify-content: center; + } + } +} + +// ============================================================================= +// ДОПОЛНИТЕЛЬНЫЕ ВАРИАНТЫ +// ============================================================================= + +// Кнопка-призрак (без фона, только border при hover) +.btn-ghost { + background: transparent; + border: 1px solid transparent; + color: var(--bs-body-color); + transition: $btn-transition; + + &:hover { + background: color-mix(in srgb, var(--bs-body-color), transparent 90%); + border-color: var(--bs-border-color); + color: var(--bs-body-color); + } + + &:active { + background: color-mix(in srgb, var(--bs-body-color), transparent 85%); + } +} + +// Pill-кнопки (максимально скругленные) +.btn-pill { + border-radius: 999px !important; +} + +// ============================================================================= +// СОСТОЯНИЯ +// ============================================================================= + +// Активное состояние для toggle-кнопок +.btn-circle[aria-expanded="true"], +.btn-circle.active { + background-color: var(--bs-primary) !important; + border-color: var(--bs-primary) !important; + color: white !important; +} + +// Анимация вращения для toggle-кнопок +.btn-circle[aria-expanded="true"] { + transform: rotate(180deg); + transition: $btn-toggle-transition; +} + +.btn-circle[aria-expanded="false"] { + transform: rotate(0deg); + transition: $btn-toggle-transition; +} + +// ============================================================================= +// ГРУППЫ КРУГЛЫХ КНОПОК +// ============================================================================= + +// Группа круглых кнопок с небольшим отступом +.btn-circle-group { + display: flex; + gap: var(--space-2); + align-items: center; + + .btn-circle { + margin: 0; + } +} + +// ============================================================================= +// ТЕМНАЯ ТЕМА +// ============================================================================= + +@media (prefers-color-scheme: dark) { + .btn-ghost { + color: var(--bs-body-color); + + &:hover { + background: color-mix(in srgb, var(--bs-body-color), transparent 92%); + } + } +} diff --git a/backend/static/scss_backup_20260115/components/_card-list.scss b/backend/static/scss_backup_20260115/components/_card-list.scss new file mode 100644 index 00000000..26d2ec21 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_card-list.scss @@ -0,0 +1,317 @@ +// ============================================================================= +// Card List Component (SCSS) +// ============================================================================= +// Универсальные стили для карточек в списках: новости, заявления, документы +// Классы feed-* применяются для любых типов контента +// +// Использование: +// - .card — контейнер карточки +// - .card-header — шапка карточки (аватар + мета) +// - .card-body — тело карточки +// - .card-actions — кнопки действий +// - .card-list — контейнер потока карточек + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +// Карточки +$card-icon-size: 42px; +$card-icon-size-sm: 36px; +$card-icon-size-compact: 32px; +$card-icon-border-radius: 9999px; + +// Переходы +$card-transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease; +$card-action-transition: background 0.2s ease, color 0.2s ease; + +// Тени +$card-hover-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + +// Границы +$card-highlighted-border: 3px solid var(--bs-primary); + +// Отступы +$feed-title-margin: var(--space-1) 0; + +// ============================================================================= +// ЗАГОЛОВОК СЕКЦИИ +// ============================================================================= + +.section-header { + display: flex; + align-items: center; + gap: var(--gap-component); + margin-bottom: var(--space-3); + + .title { + font-weight: 800; + letter-spacing: 0.2px; + margin: 0; + } +} + +// ============================================================================= +// КОНТЕЙНЕР ПОТОКА КАРТОЧЕК +// ============================================================================= + +.card-list { + display: flex; + flex-direction: column; + gap: 0; +} + +// ============================================================================= +// БАЗОВАЯ КАРТОЧКА +// ============================================================================= + +.card { + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%); + border-radius: var(--feed-radius, 18px); + background: var(--bs-body-bg); + box-shadow: var(--feed-shadow, 0 2px 8px rgba(0, 0, 0, 0.08)); + transition: $card-transition; + overflow: hidden; + + &:hover { + box-shadow: $card-hover-shadow; + transform: translateY(-1px); + } + + // Вариации карточек + &.compact { + .card-header { + padding: var(--space-1) var(--card-padding-sm); + } + + .card-body { + padding: var(--space-1) var(--card-padding-sm); + } + + .card-icon { + width: $card-icon-size-compact; + height: $card-icon-size-compact; + } + } + + &.borderless { + border: none; + box-shadow: none; + border-radius: 0; + border-bottom: 1px solid var(--bs-border-color); + + &:hover { + transform: none; + box-shadow: none; + background: var(--bs-tertiary-bg); + } + } + + &.highlighted { + border-left: $card-highlighted-border; + background: color-mix(in srgb, var(--bs-primary), transparent 95%); + } +} + +// ============================================================================= +// ШАПКА КАРТОЧКИ +// ============================================================================= + +.card-header { + display: flex; + align-items: center; + gap: var(--gap-component); + padding: var(--card-padding); + padding-bottom: var(--space-1); +} + +// Аватар +.card-icon { + width: $card-icon-size; + height: $card-icon-size; + border-radius: $card-icon-border-radius; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + background: var(--bs-tertiary-bg); + display: grid; + place-items: center; + flex: 0 0 auto; + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } +} + +// Мета-информация +.card-meta { + line-height: 1.2; + flex: 1; + min-width: 0; /* для корректного text-overflow */ +} + +.card-title { + font-weight: 700; + color: var(--bs-body-color); + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.card-subtitle { + font-size: 0.82rem; + color: var(--bs-secondary-color); + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// Значок закрепления +.feed-pin { + margin-left: auto; + font-size: 0.75rem; + padding: var(--space-1); + border-radius: 999px; + background: #fff3cd; + color: #664d03; + border: 1px solid #ffe69c; + flex-shrink: 0; + + [data-bs-theme="dark"] & { + background: #3b2e04; + color: #ffda6a; + border-color: #4f3f0a; + } +} + +// ============================================================================= +// ТЕЛО КАРТОЧКИ +// ============================================================================= + +.card-body { + padding: var(--space-1) var(--card-padding); + padding-bottom: var(--space-1); + position: relative; +} + +.feed-title { + font: 700 1.3rem/1.5 var(--bs-body-font-family); + margin: $feed-title-margin; + color: #4b4b4b; + + [data-bs-theme="dark"] & { + color: var(--bs-body-color); + } +} + +.feed-text { + font-size: 0.95rem; + line-height: 1.6; + color: var(--bs-body-color); + margin: 0; +} + +// ============================================================================= +// ИЗОБРАЖЕНИЕ В КАРТОЧКЕ +// ============================================================================= + +.feed-img { + position: relative; + border-top: 1px solid var(--bs-border-color); + background: var(--bs-tertiary-bg); + + img { + display: block; + width: 100%; + height: auto; + aspect-ratio: 16/9; + object-fit: cover; + } +} + +// ============================================================================= +// ДЕЙСТВИЯ (ЛАЙКИ, КОММЕНТАРИИ) +// ============================================================================= + +.card-actions { + display: flex; + align-items: center; + gap: var(--space-1); + padding: var(--card-padding); + padding-top: var(--space-2); + border-top: 1px solid var(--bs-border-color); + position: relative; + z-index: 2; +} + +.feed-action { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1); + border-radius: var(--radius-md, 10px); + background: transparent; + border: none; + color: var(--bs-secondary-color); + font-size: 0.875rem; + cursor: pointer; + transition: $card-action-transition; + + &:hover { + background: var(--bs-tertiary-bg); + color: var(--bs-body-color); + } + + &.active { + color: var(--bs-primary); + font-weight: 600; + } + + i { + font-size: 1rem; + } +} + +// ============================================================================= +// ФУТЕР КАРТОЧКИ +// ============================================================================= + +.feed-footer { + padding: var(--space-2) var(--card-padding); + border-top: 1px solid var(--bs-border-color); + font-size: 0.875rem; + color: var(--bs-secondary-color); +} + +// ============================================================================= +// АДАПТИВНОСТЬ +// ============================================================================= + +@media (max-width: 576px) { + .section-header { + gap: var(--space-2); + flex-wrap: wrap; + } + + .card-header { + padding: var(--space-2); + padding-bottom: var(--space-1); + } + + .card-icon { + width: $card-icon-size-sm; + height: $card-icon-size-sm; + } + + .feed-title { + font-size: 1.1rem; + } + + .card-actions { + padding: var(--space-2); + } +} diff --git a/backend/static/scss_backup_20260115/components/_cards.scss b/backend/static/scss_backup_20260115/components/_cards.scss new file mode 100644 index 00000000..bb9cf1b2 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_cards.scss @@ -0,0 +1,341 @@ +// ============================================================================= +// Cards Component +// ============================================================================= +// Glassmorphism карточки с поддержкой тёмной темы + +// ============================================================================= +// 1. БАЗОВЫЕ СТИЛИ КАРТОЧКИ +// ============================================================================= + +.card { + // Применяем glassmorphism эффект + @include glassmorphism-card(); + + // Прозрачный фон + --bs-card-bg: transparent; + background: $glass-light-bg !important; + + // Тонкая рамка + border: 1px solid $glass-light-border !important; + + // Плавные переходы + @include transition-all($transition-medium); + overflow: hidden; + + // Hover эффект - усиливаем glassmorphism + &:hover { + background: rgba(255, 255, 255, 0.15) !important; + border-color: rgba(0, 0, 0, 0.12) !important; + backdrop-filter: $blur-lg saturate(200%); + -webkit-backdrop-filter: $blur-lg saturate(200%); + box-shadow: 0 12px 48px 0 rgba(0, 0, 0, 0.15); + transform: translateY(-2px); + } +} + +// ============================================================================= +// 2. ТЁМНАЯ ТЕМА +// ============================================================================= + +[data-bs-theme="dark"] { + .card { + --bs-card-bg: transparent; + --bs-card-border-color: $glass-dark-border; + background: transparent !important; + border-color: $glass-dark-border !important; + + &:hover { + background: rgba(255, 255, 255, 0.03) !important; + border-color: rgba(255, 255, 255, 0.15) !important; + } + } +} + +// ============================================================================= +// 3. LAYOUT КОМПОНЕНТЫ КАРТОЧЕК +// ============================================================================= + +// Контейнер списка карточек +.card-list { + display: flex; + flex-direction: column; + gap: 0; +} + +// Заголовок секции +.section-header { + @include flex-align-center(); + gap: var(--gap-component, 1rem); + margin-bottom: var(--space-3, 0.75rem); + + .title { + font-weight: 800; + letter-spacing: 0.2px; + margin: 0; + } +} + +// ============================================================================= +// 4. СТРУКТУРА КАРТОЧКИ +// ============================================================================= + +// Card header +.card-header { + @include flex-align-center(); + gap: var(--gap-component, 1rem); + padding: var(--card-padding, 1rem); + padding-bottom: var(--space-1, 0.25rem); + background: transparent; + border-bottom: none; +} + +// Иконка/аватар в карточке +.card-icon { + width: 42px; + height: 42px; + @include circle(); + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + background: var(--bs-tertiary-bg); + @include flex-center(); + flex: 0 0 auto; + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } +} + +// Метаинформация +.card-meta { + line-height: 1.2; + flex: 1; + min-width: 0; +} + +// Заголовок карточки +.card-title { + font-weight: 700; + color: var(--bs-body-color); + @include text-truncate(); + display: block; + margin-bottom: 0; +} + +// Подзаголовок +.card-subtitle { + font-size: 0.82rem; + color: var(--bs-secondary-color); + @include text-truncate(); + display: block; + margin-bottom: 0; +} + +// Тело карточки +.card-body { + padding: var(--space-1, 0.25rem) var(--card-padding, 1rem); + padding-bottom: var(--space-1, 0.25rem); + position: relative; +} + +// ============================================================================= +// 5. ДЕЙСТВИЯ КАРТОЧКИ (CARD ACTIONS) +// ============================================================================= + +.card-actions { + @include flex-align-center(); + gap: var(--space-1, 0.25rem); + padding: var(--card-padding, 1rem); + padding-top: var(--space-2, 0.5rem); + border-top: 1px solid var(--bs-border-color); + position: relative; + z-index: $z-base; + + // Активные кнопки в actions + .btn, + button { + &.active, + &.is-liked { + color: var(--bs-primary) !important; + font-weight: 600; + } + } +} + +// ============================================================================= +// 6. ВАРИАЦИИ КАРТОЧЕК +// ============================================================================= + +// Компактная карточка +.card.compact { + .card-header { + padding: var(--space-1, 0.25rem) var(--card-padding-sm, 0.5rem); + } + + .card-body { + padding: var(--space-1, 0.25rem) var(--card-padding-sm, 0.5rem); + } + + .card-icon { + width: 32px; + height: 32px; + } +} + +// Карточка без рамки +.card.borderless { + border: none; + @include no-shadow(); + border-radius: 0; + border-bottom: 1px solid var(--bs-border-color); + + &:hover { + transform: none; + @include no-shadow(); + background: var(--bs-tertiary-bg); + } +} + +// Выделенная карточка +.card.highlighted { + border-left: 3px solid var(--bs-primary); + background: color-mix(in srgb, var(--bs-primary), transparent 95%); +} + +// ============================================================================= +// 7. RESPONSIVE +// ============================================================================= + +@include mobile() { + .section-header { + gap: var(--space-2, 0.5rem); + flex-wrap: wrap; + } + + .card-header { + padding: var(--space-2, 0.5rem); + padding-bottom: var(--space-1, 0.25rem); + } + + .card-icon { + width: 36px; + height: 36px; + } + + .card-actions { + padding: var(--space-2, 0.5rem); + } +} + +// ============================================================================= +// 8. СТИЛИ КНОПОК (из custom-bootstrap.scss) +// ============================================================================= + +// Базовые стили для ВСЕХ кнопок - полностью прозрачные +.btn { + background: transparent !important; + border: none !important; + color: var(--bs-secondary-color); + font-weight: 500; + @include transition(color, $transition-fast); + + &:hover:not(:disabled) { + color: var(--bs-body-color); + } + + &:active:not(:disabled) { + color: var(--bs-body-color); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +// Primary кнопка (единственная с цветным фоном) +.btn-primary { + color: $color-white !important; + background: rgba(13, 110, 253, 0.7) !important; + + &:hover:not(:disabled) { + background: rgba(13, 110, 253, 0.85) !important; + box-shadow: 0 4px 16px rgba(13, 110, 253, 0.3); + } + + &:active:not(:disabled) { + background: rgba(13, 110, 253, 0.95) !important; + } +} + +// Secondary, outline, ghost - все прозрачные +.btn-secondary, +.btn-outline-primary, +.btn-outline-secondary, +.btn-ghost { + background: transparent !important; + color: var(--bs-secondary-color); + + &:hover:not(:disabled) { + background: transparent !important; + color: var(--bs-body-color); + } + + &:active:not(:disabled) { + background: transparent !important; + } +} + +// Особый цвет для outline-primary +.btn-outline-primary { + color: var(--bs-primary); + + &:hover:not(:disabled) { + color: var(--bs-primary); + background: transparent !important; + } +} + +// Кнопки в тёмной теме +[data-bs-theme="dark"] { + .btn { + &:hover:not(:disabled), + &:active:not(:disabled) { + background: transparent !important; + } + } + + .btn-primary { + background: rgba(13, 110, 253, 0.6) !important; + + &:hover:not(:disabled) { + background: rgba(13, 110, 253, 0.75) !important; + } + + &:active:not(:disabled) { + background: rgba(13, 110, 253, 0.85) !important; + } + } + + .btn-secondary, + .btn-outline-primary, + .btn-outline-secondary, + .btn-ghost { + &:hover:not(:disabled), + &:active:not(:disabled) { + background: transparent !important; + } + } +} + +// ============================================================================= +// 9. УТИЛИТАРНЫЕ КЛАССЫ +// ============================================================================= + +// Моноширинный шрифт для дат, цифр, кодов +.text-mono { + font-variant-numeric: tabular-nums; + font-family: ui-monospace, 'SF Mono', SFMono-Regular, 'Consolas', 'Liberation Mono', 'Menlo', 'Courier', monospace; +} diff --git a/backend/static/scss_backup_20260115/components/_comments.scss b/backend/static/scss_backup_20260115/components/_comments.scss new file mode 100644 index 00000000..bb17b850 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_comments.scss @@ -0,0 +1,152 @@ +// ============================================================================= +// Comments Component +// ============================================================================= +// Единый стиль для комментариев во всех местах приложения: +// - В карточках постов (_feed_cards.html) +// - На странице деталей поста (post_detail.html) +// - В модальном окне (postDetailModal.js) + +.comment-item { + padding: 0.5rem 0; // py-2 + display: flex; + flex-direction: column; + align-items: flex-start; + + .comment-header { + font-size: 0.875rem; // small + margin-bottom: 0.25rem; + display: flex; + align-items: center; + flex-wrap: wrap; + + .comment-author { + font-weight: 600; // fw-semibold + text-decoration: none; + color: inherit; + + &:hover { + text-decoration: underline; + } + } + + .comment-separator { + color: var(--bs-secondary); + margin: 0 0.25rem; + } + + .comment-date { + color: var(--bs-secondary); + } + + .badge { + font-size: 0.7rem; + padding: 0.15rem 0.4rem; + } + } + + .comment-body { + font-size: 0.875rem; // small + line-height: 1.5; + white-space: pre-wrap; + word-wrap: break-word; + background: var(--bs-tertiary-bg); + padding: 0.5rem 0.75rem; + border-radius: 12px; + max-width: 80%; + } + + // Комментарии автора поста справа + &.comment-item--author { + align-items: flex-end; + + .comment-header { + flex-direction: row-reverse; + + .comment-separator { + order: 0; + } + + .comment-date { + order: -1; + } + } + + .comment-body { + background: var(--bs-primary-bg-subtle, rgba(13, 110, 253, 0.1)); + border: 1px solid var(--bs-primary-border-subtle, rgba(13, 110, 253, 0.2)); + } + } +} + +// Контейнер для списка комментариев +.comments-list { + margin-bottom: 1rem; +} + +// Заголовок секции комментариев +.comments-section-header { + display: flex; + align-items: center; + margin-bottom: 0.5rem; + + .comments-icon { + font-size: 1.5rem; + color: var(--bs-primary); + margin-right: 0.5rem; + } + + .comments-title { + margin-bottom: 0; + } + + .comments-count { + font-size: 0.875rem; + color: var(--bs-secondary); + margin-left: 0.5rem; + } +} + +// Обёртка для комментариев с sticky формой +.comments-section-wrapper { + position: relative; +} + +// Скроллируемая область с комментариями +.comments-scrollable { + max-height: 400px; + overflow-y: auto; + overflow-x: hidden; + padding-bottom: 1rem; + + // Firefox скроллбар + scrollbar-width: thin; + scrollbar-color: rgba(0, 0, 0, 0.15) transparent; + + // Стилизация скроллбара (Chrome, Safari, Edge) + &::-webkit-scrollbar { + width: 8px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.15); + border-radius: 4px; + + &:hover { + background: rgba(0, 0, 0, 0.25); + } + } +} + +// Sticky форма для комментариев +.comment-form-sticky { + position: sticky; + bottom: 0; + background: var(--bs-body-bg); + z-index: 10; + border-top: 1px solid var(--bs-border-color); + padding-top: 1rem; +} diff --git a/backend/static/scss_backup_20260115/components/_common.scss b/backend/static/scss_backup_20260115/components/_common.scss new file mode 100644 index 00000000..01a2a543 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_common.scss @@ -0,0 +1,327 @@ +/** + * _common.scss + * Общие переиспользуемые классы для всех компонентов + * Избегает дублирования между компонентами + */ + +/* ───────────────────────────────────────────────────────────── + BUTTONS +────────────────────────────────────────────────────────────── */ + +/* Иконочная кнопка */ +.btn-icon { + width: 36px; + height: 36px; + display: inline-grid; + place-items: center; + border: 1px solid var(--bs-border-color); + border-radius: var(--button-radius, 10px); + background: transparent; + color: var(--bs-body-color); + cursor: pointer; + transition: all var(--transition-fast, 0.15s); + + &:hover { + background: var(--bs-tertiary-bg); + border-color: var(--bs-border-color); + } + + &--primary { + color: var(--bs-primary); + } + + &--danger { + color: var(--bs-danger); + } + + &--success { + color: var(--bs-success); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +/* ───────────────────────────────────────────────────────────── + BADGES +────────────────────────────────────────────────────────────── */ + +/* Бейдж подтверждения */ +.badge-acked { + background: color-mix(in srgb, var(--bs-success) 20%, transparent); + color: var(--bs-success); + border: 1px solid color-mix(in srgb, var(--bs-success) 30%, transparent); + padding: 0.28rem 0.55rem; + border-radius: var(--badge-radius, 999px); + font-size: 0.8rem; + font-weight: 500; + + [data-bs-theme="dark"] & { + background: color-mix(in srgb, var(--bs-success) 15%, #000); + color: color-mix(in srgb, var(--bs-success) 80%, #fff); + border-color: color-mix(in srgb, var(--bs-success) 25%, transparent); + } +} + +/* Бейдж статуса */ +.badge-status { + padding: var(--space-1); + border-radius: var(--badge-radius, 999px); + font-size: 0.75rem; + font-weight: 600; + border: 1px solid; + + &--pending { + background: color-mix(in srgb, var(--bs-warning) 15%, transparent); + color: var(--bs-warning); + border-color: color-mix(in srgb, var(--bs-warning) 30%, transparent); + } + + &--approved { + background: color-mix(in srgb, var(--bs-success) 15%, transparent); + color: var(--bs-success); + border-color: color-mix(in srgb, var(--bs-success) 30%, transparent); + } + + &--rejected { + background: color-mix(in srgb, var(--bs-danger) 15%, transparent); + color: var(--bs-danger); + border-color: color-mix(in srgb, var(--bs-danger) 30%, transparent); + } +} + +/* ───────────────────────────────────────────────────────────── + PINNED BADGE (централизованный) +────────────────────────────────────────────────────────────── */ +.chat-pinned-badge { + position: absolute; + background: var(--bs-warning); + color: white; + @include circle(); + @include flex-center(); + z-index: 1; + + // Размер по умолчанию (средний) + width: 22px; + height: 22px; + top: 6px; + right: 6px; + font-size: 0.75rem; + + // Маленький вариант (для списка чатов) + &--sm { + width: 20px; + height: 20px; + top: 4px; + right: 4px; + font-size: 0.7rem; + } + + // Большой вариант (для детального чата) + &--lg { + width: 24px; + height: 24px; + top: 8px; + right: 8px; + font-size: 0.8rem; + } +} + +/* ───────────────────────────────────────────────────────────── + AVATARS +────────────────────────────────────────────────────────────── */ + +.avatar { + border-radius: 50%; + overflow: hidden; + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--bs-tertiary-bg); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + flex-shrink: 0; + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } + + i { + color: var(--bs-secondary-color); + } +} + +/* Размеры */ +.avatar-xs { + width: var(--avatar-xs, 28px); + height: var(--avatar-xs, 28px); +} + +.avatar-sm { + width: var(--avatar-sm, 36px); + height: var(--avatar-sm, 36px); +} + +.avatar-md { + width: var(--avatar-md, 44px); + height: var(--avatar-md, 44px); +} + +.avatar-lg { + width: var(--avatar-lg, 64px); + height: var(--avatar-lg, 64px); +} + +.avatar-xl { + width: var(--avatar-xl, 96px); + height: var(--avatar-xl, 96px); +} + +/* ───────────────────────────────────────────────────────────── + FEED AUTHOR & META +────────────────────────────────────────────────────────────── */ + +.card-title { + font-weight: 700; + color: var(--bs-body-color); + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.card-subtitle { + font-size: 0.82rem; + color: var(--bs-secondary-color); + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + a { + color: inherit; + text-decoration: none; + + &:hover { + color: var(--bs-primary); + } + } +} + +/* ───────────────────────────────────────────────────────────── + AVATARS (Centralized) +────────────────────────────────────────────────────────────── */ + +.chat-avatar, +.avatar { + @include circle(); + overflow: hidden; + flex-shrink: 0; + background: var(--bs-tertiary-bg); + border: 1px solid var(--bs-border-color); + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } +} + +// Size variants +.chat-avatar, +.avatar { + width: $avatar-md; + height: $avatar-md; +} + +.chat-avatar--sm, +.avatar--sm { + width: $avatar-sm; + height: $avatar-sm; +} + +.chat-avatar--lg, +.avatar--lg { + width: $avatar-lg; + height: $avatar-lg; +} + +.chat-avatar--xl, +.avatar--xl { + width: $avatar-xl; + height: $avatar-xl; +} + +/* ───────────────────────────────────────────────────────────── + CHIPS +────────────────────────────────────────────────────────────── */ + +.chip { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1); + border-radius: var(--badge-radius, 999px); + background: var(--bs-tertiary-bg); + border: 1px solid var(--bs-border-color); + font-size: 0.82rem; + color: var(--bs-body-color); +} + +.chip-remove { + cursor: pointer; + color: var(--bs-danger); + font-weight: 700; + line-height: 1; + + &:hover { + color: color-mix(in srgb, var(--bs-danger) 80%, #000); + } +} + +/* ───────────────────────────────────────────────────────────── + UTILITIES +────────────────────────────────────────────────────────────── */ + +/* Текст с многоточием */ +.text-truncate-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.text-truncate-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Кастомный scrollbar */ +.custom-scrollbar { + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: color-mix(in srgb, var(--bs-border-color) 70%, transparent); + border-radius: 10px; + + &:hover { + background: var(--bs-border-color); + } + } +} diff --git a/backend/static/scss_backup_20260115/components/_department-controls.scss b/backend/static/scss_backup_20260115/components/_department-controls.scss new file mode 100644 index 00000000..8d69f3dc --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_department-controls.scss @@ -0,0 +1,26 @@ +// ============================================================================= +// Department Controls (SCSS) +// ============================================================================= +// Стили для модального управления отделом + +// ============================================================================= +// MODAL Z-INDEX OVERRIDES +// ============================================================================= + +// Модалки поверх всего +.modal-backdrop.show { + z-index: 3000 !important; +} + +.modal.show { + z-index: 3010 !important; +} + +.modal-dialog { + pointer-events: auto !important; +} + +// Портальный dropdown автокомплита ролей +.role-portal { + z-index: 1800 !important; +} diff --git a/backend/static/scss_backup_20260115/components/_document-list.scss b/backend/static/scss_backup_20260115/components/_document-list.scss new file mode 100644 index 00000000..3c770b8c --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_document-list.scss @@ -0,0 +1,248 @@ +// ============================================================================= +// Document List Styles (SCSS) +// ============================================================================= +// Стили списка документов, recipient picker, и подтверждений + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +$doc-icon-size: 28px; +$doc-row-padding: var(--space-2); +$doc-border-unacked-width: 3px; +$recipient-avatar-size: 26px; +$card-icon-size: 32px; + +// Цвета +$doc-hover-primary: 4%; +$doc-unacked-bg: 8%; +$doc-unacked-hover: 15%; + +// ============================================================================= +// СПИСОК ДОКУМЕНТОВ +// ============================================================================= + +#docList.card-list { + display: block; + gap: 0 !important; +} + +#docList .doc-row { + padding: $doc-row-padding !important; + margin: 0 !important; + border-bottom: 1px solid var(--bs-border-color); + transition: background-color 0.2s ease; + cursor: pointer; + + &:hover { + background-color: color-mix(in srgb, var(--bs-primary) $doc-hover-primary, transparent); + } + + &.doc-unacked { + background-color: color-mix(in srgb, var(--bs-warning) $doc-unacked-bg, transparent); + border-left: $doc-border-unacked-width solid var(--bs-warning); + padding-left: calc($doc-row-padding - $doc-border-unacked-width) !important; + + &:hover { + background-color: color-mix(in srgb, var(--bs-warning) $doc-unacked-hover, transparent); + } + } + + &:last-child { + border-bottom: 0; + } +} + +// ============================================================================= +// ЗАГОЛОВОК ДОКУМЕНТА +// ============================================================================= + +#docList .card-header { + display: grid; + grid-template-columns: $doc-icon-size 1fr auto; + align-items: center; + gap: 0.75rem; + margin: 0; + padding: 0 !important; +} + +#docList .feed-ico { + width: $doc-icon-size; + height: $doc-icon-size; + display: grid; + place-items: center; + border-radius: 8px; + background: color-mix(in srgb, var(--bs-body-bg) 90%, transparent); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%); + color: var(--bs-primary); +} + +// ============================================================================= +// ДЕЙСТВИЯ С ДОКУМЕНТАМИ +// ============================================================================= + +.doc-actions { + display: flex; + align-items: center; + gap: 0.4rem; + flex-wrap: wrap; +} + +// ============================================================================= +// SCOPE SWITCH +// ============================================================================= + +.scope-switch .btn { + min-height: 36px; + border-radius: 999px; +} + +// ============================================================================= +// АДАПТИВНОСТЬ: МОБИЛЬНЫЕ +// ============================================================================= + +@media (max-width: 576px) { + #docList .card-header { + grid-template-columns: $doc-icon-size 1fr; + gap: 0.5rem; + } + + .doc-actions { + grid-column: 2; + margin-top: 0.5rem; + justify-content: flex-start; + + .btn, + .btn-icon { + font-size: 0.85rem; + padding: 0.35rem 0.65rem; + } + } +} + +// ============================================================================= +// RECIPIENT PICKER +// ============================================================================= + +.recipient-picker { + border: 1px solid var(--bs-border-color); + border-radius: 12px; + padding: var(--space-2); + background: var(--bs-body-bg); +} + +.recipient-picker .rp-selected { + display: flex; + flex-wrap: wrap; + gap: var(--space-1); + margin-bottom: var(--space-1); +} + +.recipient-picker .rp-chip { + display: inline-flex; + align-items: center; + gap: var(--space-1); + border: 1px solid var(--bs-border-color); + border-radius: 999px; + padding: var(--space-1); + font-size: 0.85rem; + + button { + border: 0; + background: transparent; + padding: 0 var(--space-1); + line-height: 1; + } +} + +.recipient-picker .rp-search { + position: relative; + + input { + width: 100%; + } +} + +.recipient-picker .rp-results { + margin-top: 0.5rem; + max-height: 220px; + overflow: auto; + border: 1px solid var(--bs-border-color); + border-radius: 10px; +} + +.recipient-picker .rp-item { + display: flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1); + border-bottom: 1px solid var(--bs-border-color); + + &:last-child { + border-bottom: 0; + } + + small { + color: var(--bs-secondary-color); + } +} + +.recipient-picker .rp-empty { + padding: var(--space-1); + text-align: center; + color: var(--bs-secondary-color); +} + +.recipient-picker .rp-avatar { + width: $recipient-avatar-size; + height: $recipient-avatar-size; + border-radius: 50%; + background: color-mix(in srgb, var(--bs-primary) 12%, transparent); + display: inline-grid; + place-items: center; + font-size: 0.7rem; +} + +.recipient-block[hidden] { + display: none !important; +} + +// ============================================================================= +// ACKNOWLEDGMENTS (ПОДТВЕРЖДЕНИЯ) +// ============================================================================= + +.acks-list { + max-height: 50vh; + overflow: auto; + margin: 0; + padding-left: 1.1rem; +} + +.acks-empty { + color: var(--bs-secondary-color); +} + +.acks-more { + margin-top: 0.5rem; +} + +// ============================================================================= +// ИКОНКИ +// ============================================================================= + +.card-icon { + width: $card-icon-size; + height: $card-icon-size; + flex: 0 0 $card-icon-size; + border-radius: 50%; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +.rp-avatar { + width: 100%; + height: 100%; + object-fit: cover; +} diff --git a/backend/static/scss_backup_20260115/components/_feed-specific.scss b/backend/static/scss_backup_20260115/components/_feed-specific.scss new file mode 100644 index 00000000..b96d4f77 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_feed-specific.scss @@ -0,0 +1,134 @@ +// ============================================================================= +// Feed-Specific Styles (SCSS) +// ============================================================================= +// Стили, специфичные для постов в ленте новостей + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +$feed-pin-bg-light: #fff3cd; +$feed-pin-color-light: #664d03; +$feed-pin-border-light: #ffe69c; +$feed-pin-bg-dark: #3b2e04; +$feed-pin-color-dark: #ffda6a; +$feed-pin-border-dark: #4f3f0a; + +$feed-title-color-light: #4b4b4b; + +// ============================================================================= +// ЗНАЧОК ЗАКРЕПЛЕНИЯ +// ============================================================================= + +.feed-pin { + margin-left: auto; + font-size: 0.75rem; + padding: var(--space-1, 0.25rem); + border-radius: 999px; + background: $feed-pin-bg-light; + color: $feed-pin-color-light; + border: 1px solid $feed-pin-border-light; + flex-shrink: 0; + + [data-bs-theme="dark"] & { + background: $feed-pin-bg-dark; + color: $feed-pin-color-dark; + border-color: $feed-pin-border-dark; + } +} + +// ============================================================================= +// ЗАГОЛОВОК ПОСТА +// ============================================================================= + +.feed-title { + font: 700 1.3rem/1.5 var(--bs-body-font-family); + margin: var(--space-1, 0.25rem) 0; + color: $feed-title-color-light; + + [data-bs-theme="dark"] & { + color: var(--bs-body-color); + } +} + +// ============================================================================= +// ТЕКСТ ПОСТА +// ============================================================================= + +.feed-text { + font-size: 0.95rem; + line-height: 1.6; + color: var(--bs-body-color); + margin: 0; +} + +// ============================================================================= +// ИЗОБРАЖЕНИЕ ПОСТА +// ============================================================================= + +.feed-img { + position: relative; + border-top: 1px solid var(--bs-border-color); + background: var(--bs-tertiary-bg); + + img { + display: block; + width: 100%; + height: auto; + aspect-ratio: 16/9; + object-fit: cover; + } +} + +// ============================================================================= +// КНОПКИ ДЕЙСТВИЙ (ЛАЙКИ, КОММЕНТАРИИ) +// ============================================================================= + +.feed-action { + display: inline-flex; + align-items: center; + gap: var(--space-1, 0.25rem); + padding: var(--space-1, 0.25rem); + border-radius: var(--radius-md, 10px); + background: transparent; + border: none; + color: var(--bs-secondary-color); + font-size: 0.875rem; + cursor: pointer; + transition: background 0.2s ease, color 0.2s ease; + + &:hover { + background: var(--bs-tertiary-bg); + color: var(--bs-body-color); + } + + &.active { + color: var(--bs-primary); + font-weight: 600; + } + + i { + font-size: 1rem; + } +} + +// ============================================================================= +// ФУТЕР ПОСТА +// ============================================================================= + +.feed-footer { + padding: var(--space-2, 0.5rem) var(--card-padding, 1rem); + border-top: 1px solid var(--bs-border-color); + font-size: 0.875rem; + color: var(--bs-secondary-color); +} + +// ============================================================================= +// АДАПТИВНОСТЬ +// ============================================================================= + +@media (max-width: 576px) { + .feed-title { + font-size: 1.1rem; + } +} diff --git a/backend/static/scss_backup_20260115/components/_ios-components.scss b/backend/static/scss_backup_20260115/components/_ios-components.scss new file mode 100644 index 00000000..076f5fd7 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_ios-components.scss @@ -0,0 +1,546 @@ +// ============================================================================= +// iOS-style Components (SCSS) +// ============================================================================= +// iOS-style компоненты: карточки, списки, аккордеоны, badges +// +// Зависимости: +// - variables.css (--feed-radius, --feed-shadow, --navbar-h) +// - common.css (.chip определён там) + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +// Карточки +$ios-card-border-radius: var(--feed-radius); +$ios-card-transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease; +$ios-card-hover-transform: translateY(-1px); + +// Аватар +$avatar-size: 132px; +$avatar-border-radius: 28px; +$avatar-font-size: 3rem; + +// Списки +$ios-list-border-radius: 14px; +$ios-cell-gap: 0.75rem; +$ios-cell-padding: 0.85rem 1rem; + +// Badges +$badge-border-radius: 999px; +$badge-font-size: 0.8rem; +$badge-icon-size: 0.95rem; + +// Аккордеон +$ios-acc-border-radius: 14px; + +// Collapse +$collapse-transition: height 0.35s ease; +$collapse-caret-transition: transform 0.25s ease; + +// Контейнер +$container-xxl-max-width: 1820px; + +// ============================================================================= +// МИКСИНЫ +// ============================================================================= + +// Миксин для цветовых badge-ов +@mixin badge-variant($color) { + background: color-mix(in srgb, var(--bs-#{$color}) 15%, var(--bs-body-bg)); + color: color-mix(in srgb, var(--bs-#{$color}) 60%, black 0%); + border: 1px solid color-mix(in srgb, var(--bs-#{$color}) 35%, transparent); + + &:hover { + background: color-mix(in srgb, var(--bs-#{$color}) 20%, var(--bs-body-bg)); + } +} + +// ============================================================================= +// BASE CARD +// ============================================================================= + +.ios-card { + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%); + border-radius: $ios-card-border-radius; + background: var(--bs-body-bg); + box-shadow: var(--feed-shadow); + transition: $ios-card-transition; + overflow: hidden; + + &:hover { + box-shadow: var(--feed-shadow-hover); + transform: $ios-card-hover-transform; + } +} + +// ============================================================================= +// HEADER +// ============================================================================= + +.ios-header { + position: sticky; + top: var(--navbar-h, 0px); + z-index: 5; + padding: var(--space-2) 0; + backdrop-filter: saturate(180%) blur(12px); + background: color-mix(in srgb, var(--bs-body-bg) 85%, transparent); + border-bottom: none !important; +} + +.ios-title { + font-weight: 800; + letter-spacing: -0.01em; +} + +.row.g-ios { + align-items: flex-start; +} + +// ============================================================================= +// AVATAR +// ============================================================================= + +.ios-ava, +.ios-ava--ph { + display: grid; + place-items: center; + background: var(--bs-tertiary-bg); + color: var(--bs-secondary-color); + font-size: $avatar-font-size; + line-height: 1; + width: 100%; + height: 100%; + border-radius: $avatar-border-radius; + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); +} + +.ava-wrap { + position: relative; + width: $avatar-size; + height: $avatar-size; + margin: 0 auto var(--space-2); + display: block; +} + +// ============================================================================= +// NAME & ROLE +// ============================================================================= + +.ios-name { + font-weight: 800; + font-size: 1.25rem; +} + +.ios-role { + font-size: 0.9rem; + color: var(--bs-secondary-color); +} + +// ============================================================================= +// CHIPS +// ============================================================================= + +// Base .chip определён в common.css +.chips { + display: flex; + flex-wrap: wrap; + gap: var(--space-1); +} + +// ============================================================================= +// COLLAPSE TRIGGERS +// ============================================================================= + +.ios-collapse-trigger { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-2); + padding: var(--space-1) var(--space-2); + cursor: pointer; + border-radius: $ios-list-border-radius; + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + background: color-mix(in srgb, var(--bs-body-bg) 94%, transparent); + color: var(--bs-body-color); +} + +.ios-collapse-caret { + transition: $collapse-caret-transition; +} + +.ios-collapse-trigger[aria-expanded="true"] .ios-collapse-caret { + transform: rotate(180deg); +} + +.ios-collapse-trigger + .collapse { + margin-top: 0 !important; + + > .ios-list, + > .chips { + margin-top: 0.5rem; + } +} + +.collapsing { + overflow: hidden; + transition: $collapse-transition; +} + +// ============================================================================= +// LISTS +// ============================================================================= + +.ios-list { + border-radius: $ios-list-border-radius; + overflow: hidden; + background: var(--bs-body-bg); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%); +} + +.ios-cell { + display: flex; + align-items: center; + gap: $ios-cell-gap; + padding: $ios-cell-padding; + border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%); + flex-wrap: wrap; + + &:last-child { + border-bottom: 0; + } + + i { + width: 1.25rem; + text-align: center; + opacity: 0.9; + } + + a { + text-decoration: none; + color: inherit; + } + + // Departments + &.dept-item { + justify-content: space-between; + + > .d-flex { + flex: 0 1 auto; + flex-wrap: wrap; + justify-content: flex-end; + gap: 0.35rem 0.5rem; + max-width: 100%; + } + + &.is-editing .dept-role { + display: none !important; + } + } + + &.dept-head { + border-left: 0; + background: transparent; + } +} + +.ios-section-title { + font-weight: 700; + font-size: 1rem; + margin-bottom: 0.5rem; +} + +.rounded-ios { + border-radius: $ios-list-border-radius !important; +} + +// ============================================================================= +// DEPARTMENTS LIST +// ============================================================================= + +.dept-line { + display: flex; + align-items: center; + gap: 0.5rem; + min-width: 0; +} + +.dept-name { + font-weight: 700; +} + +.dept-role { + color: var(--bs-secondary-color); +} + +// ============================================================================= +// BADGES (HEAD & GEAR) +// ============================================================================= + +.badge-head, +.badge-gear { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1); + border-radius: $badge-border-radius; + font-weight: 700; + font-size: $badge-font-size; + line-height: 1; + white-space: nowrap; + flex: 0 0 auto; + + .bi { + font-size: $badge-icon-size; + line-height: 1; + } +} + +.badge-head { + @include badge-variant(warning); +} + +.badge-gear { + @include badge-variant(primary); + cursor: pointer; +} + +// Скрытие текста на мобильных +@media (max-width: 420px) { + .badge-head .lbl, + .badge-gear .lbl { + display: none; + } +} + +@media (max-width: 576px) { + .badge-head .lbl, + .badge-gear .lbl { + display: none; + } +} + +// ============================================================================= +// ACCORDION (IOS-ACC) +// ============================================================================= + +.ios-acc { + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + border-radius: $ios-acc-border-radius; + background: var(--bs-body-bg); + overflow: hidden; + + & + & { + margin-top: var(--space-2); + } + + .ios-collapse-trigger { + border: 0; + border-radius: 0; + background: transparent; + width: 100%; + padding: var(--space-2); + + &:focus { + box-shadow: none; + } + + & + .collapse { + margin-top: 0 !important; + } + } + + .collapse { + margin: 0 !important; + } + + .ios-list { + border: 0; + border-radius: 0; + } + + .chips { + margin: 0; + } + + .ios-acc-body { + padding: var(--space-2); + border-top: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + } + + .ios-collapse-trigger + .collapse { + margin-top: 0 !important; + } + + .ios-acc-body { + border-top: 0; + } +} + +// ============================================================================= +// ACTIVITY BLOCK +// ============================================================================= + +#activity { + .p-4 { + padding: 0 !important; + } + + .ios-section-title { + padding: var(--space-2) var(--space-2) var(--space-1) !important; + margin: 0 !important; + } + + .activity-feed { + margin: 0 !important; + display: flex; + flex-direction: column; + gap: 1rem; + + .card { + width: 100% !important; + max-width: none !important; + border: 0 !important; + box-shadow: none !important; + border-radius: 0 !important; + margin: 0 !important; + } + + .card-list { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .row > * { + flex: 0 0 100% !important; + max-width: 100% !important; + } + } + + .card-actions { + border-top: 0 !important; + } + + .collapse .bg-body-tertiary.border { + border: 0 !important; + } +} + +// ============================================================================= +// SKILLS UI +// ============================================================================= + +#skillsBlock[data-editable="1"] { + .chip.chip-add { + display: inline-flex; + align-items: center; + cursor: pointer; + border-style: dashed; + + &.pulse { + transform: scale(0.97); + transition: transform 0.15s ease; + } + + .bi { + font-size: 0.95rem; + } + } + + .chip.chip-skill:hover { + .lbl { + opacity: 0; + } + + .chip-del-btn, + &:focus-within .chip-del-btn { + visibility: visible; + opacity: 1; + } + } +} + +.chip.chip-skill { + position: relative; + padding: 0.35rem 0.65rem; + + .lbl { + transition: opacity 0.15s ease; + } + + .chip-del-btn { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: 0; + padding: 0; + margin: 0; + cursor: pointer; + color: var(--bs-danger); + font-size: 1rem; + line-height: 1; + visibility: hidden; + opacity: 0; + transition: opacity 0.15s ease; + z-index: 1; + + .bi { + pointer-events: none; + } + } +} + +.skills-empty { + color: var(--bs-secondary-color); + font-size: 0.875rem; + margin-right: 0.25rem; +} + +// ============================================================================= +// DEPT INLINE EDITOR +// ============================================================================= + +.dept-edit-btn { + width: 32px; + height: 32px; + padding: 0; + display: grid; + place-items: center; + border-radius: $badge-border-radius; + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%); + background: color-mix(in srgb, var(--bs-body-bg) 94%, transparent); + + &:hover { + background: color-mix(in srgb, var(--bs-body-bg) 88%, transparent); + } +} + +.dept-editor-wrap { + flex: 0 0 100%; + width: 100%; + margin-top: 0.5rem; +} + +.dept-editor form { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.dept-role-select { + min-width: 180px; +} + +// ============================================================================= +// CONTAINER +// ============================================================================= + +.container-xxl { + max-width: $container-xxl-max-width; + padding-inline: var(--bs-gutter-x, var(--space-2)); +} diff --git a/backend/static/scss_backup_20260115/components/_ios-search.scss b/backend/static/scss_backup_20260115/components/_ios-search.scss new file mode 100644 index 00000000..8f095452 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_ios-search.scss @@ -0,0 +1,251 @@ +// ============================================================================= +// iOS-style Search Component (SCSS) +// ============================================================================= +// Поле поиска в стиле iOS с иконкой лупы и кнопкой очистки +// Переиспользуемый компонент для различных страниц списков +// +// HTML структура: +// + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +// Размеры (default) +$ios-search-height: 40px; +$ios-search-max-width: 420px; +$ios-search-border-radius: 14px; +$ios-search-font-size: 15px; + +// Размеры sm +$ios-search-height-sm: 34px; +$ios-search-border-radius-sm: 12px; +$ios-search-font-size-sm: 14px; + +// Размеры lg +$ios-search-height-lg: 48px; +$ios-search-border-radius-lg: 16px; +$ios-search-font-size-lg: 16px; + +// Размеры mobile +$ios-search-height-mobile: 38px; +$ios-search-font-size-mobile: 14px; + +// Отступы +$ios-search-padding-left: 36px; +$ios-search-padding-right: 36px; +$ios-search-icon-left: 10px; +$ios-search-clear-right: 4px; + +$ios-search-padding-left-sm: 32px; +$ios-search-padding-right-sm: 32px; +$ios-search-icon-left-sm: 8px; +$ios-search-clear-right-sm: 3px; + +$ios-search-padding-left-lg: 42px; +$ios-search-padding-right-lg: 42px; +$ios-search-icon-left-lg: 12px; +$ios-search-clear-right-lg: 6px; + +// Иконки +$ios-search-icon-size: 0.95rem; +$ios-search-icon-size-sm: 0.85rem; +$ios-search-icon-size-lg: 1.1rem; +$ios-search-clear-icon-size: 0.9rem; + +// Переходы +$ios-search-transition: box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease; +$ios-search-btn-transition: background 0.2s ease; + +// ============================================================================= +// МИКСИНЫ +// ============================================================================= + +// Миксин для скрытия нативного крестика браузера +@mixin hide-search-controls { + &::-webkit-search-cancel-button, + &::-webkit-search-decoration { + display: none; + -webkit-appearance: none; + } + + &::-ms-clear { + display: none; + } +} + +// ============================================================================= +// БАЗОВЫЕ СТИЛИ +// ============================================================================= + +// Контейнер поиска +.ios-search { + position: relative; + width: 100%; + max-width: $ios-search-max-width; +} + +// Поле ввода +.ios-search-input { + width: 100%; + height: $ios-search-height; + padding: var(--space-1) $ios-search-padding-right var(--space-1) $ios-search-padding-left; + border-radius: $ios-search-border-radius; + border: 1px solid var(--bs-border-color); + background: color-mix(in srgb, var(--bs-body-bg), #000 4%); + font-size: $ios-search-font-size; + outline: none; + transition: $ios-search-transition; + + // Скрываем нативный крестик браузера + @include hide-search-controls; + + // Placeholder + &::placeholder { + color: var(--bs-secondary-color); + opacity: 0.6; + } + + // Фокус + &:focus { + border-color: var(--bs-primary); + box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.15); + background: var(--bs-body-bg); + } + + // Темная тема + [data-bs-theme="dark"] & { + background: color-mix(in srgb, var(--bs-body-bg), #fff 6%); + } +} + +// ============================================================================= +// ИКОНКА ПОИСКА (ЛУПА) +// ============================================================================= + +.ios-search > .bi-search { + position: absolute; + left: $ios-search-icon-left; + top: 50%; + transform: translateY(-50%); + opacity: 0.55; + pointer-events: none; + font-size: $ios-search-icon-size; + color: var(--bs-body-color); +} + +// ============================================================================= +// КНОПКА ОЧИСТКИ +// ============================================================================= + +.ios-search-clear { + position: absolute; + right: $ios-search-clear-right; + top: 50%; + transform: translateY(-50%); + border: 0; + background: transparent; + padding: var(--space-1); + display: none; + cursor: pointer; + border-radius: 50%; + transition: $ios-search-btn-transition; + + &:hover { + background: color-mix(in srgb, var(--bs-body-color), transparent 90%); + } + + &:active { + background: color-mix(in srgb, var(--bs-body-color), transparent 85%); + } + + .bi-x-lg { + opacity: 0.6; + font-size: $ios-search-clear-icon-size; + color: var(--bs-body-color); + } +} + +// Показываем кнопку очистки когда есть текст +.ios-search-input:not(:placeholder-shown) ~ .ios-search-clear { + display: block; +} + +// ============================================================================= +// ВАРИАЦИИ РАЗМЕРОВ +// ============================================================================= + +// Маленький размер +.ios-search.sm { + .ios-search-input { + height: $ios-search-height-sm; + font-size: $ios-search-font-size-sm; + padding: var(--space-1) $ios-search-padding-right-sm var(--space-1) $ios-search-padding-left-sm; + border-radius: $ios-search-border-radius-sm; + } + + > .bi-search { + left: $ios-search-icon-left-sm; + font-size: $ios-search-icon-size-sm; + } + + .ios-search-clear { + right: $ios-search-clear-right-sm; + padding: var(--space-1); + } +} + +// Большой размер +.ios-search.lg { + .ios-search-input { + height: $ios-search-height-lg; + font-size: $ios-search-font-size-lg; + padding: var(--space-2) $ios-search-padding-right-lg var(--space-2) $ios-search-padding-left-lg; + border-radius: $ios-search-border-radius-lg; + } + + > .bi-search { + left: $ios-search-icon-left-lg; + font-size: $ios-search-icon-size-lg; + } + + .ios-search-clear { + right: $ios-search-clear-right-lg; + padding: var(--space-1); + } +} + +// ============================================================================= +// ВАРИАЦИЯ: ТОЛЬКО ИКОНКА БЕЗ КНОПКИ ОЧИСТКИ +// ============================================================================= + +.ios-search.icon-only { + .ios-search-input { + padding-right: var(--space-3); + } + + .ios-search-clear { + display: none !important; + } +} + +// ============================================================================= +// АДАПТИВНОСТЬ +// ============================================================================= + +@media (max-width: 576px) { + .ios-search { + max-width: 100%; + + .ios-search-input { + height: $ios-search-height-mobile; + font-size: $ios-search-font-size-mobile; + } + } +} diff --git a/backend/static/scss_backup_20260115/components/_join-scroller.scss b/backend/static/scss_backup_20260115/components/_join-scroller.scss new file mode 100644 index 00000000..ef0c5f74 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_join-scroller.scss @@ -0,0 +1,219 @@ +// ============================================================================= +// Join Scroller Component (SCSS) +// ============================================================================= +// Горизонтальная прокручиваемая лента новых сотрудников с hover-эффектами + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +$join-base-size: 56px; // базовый размер аватара +$join-multiplier: 4; // множитель при раскрытии +$join-multiplier-mobile: 3.2; // множитель на мобильных +$join-gap: 0.9rem; +$join-padding: 0.25rem; +$join-transition-duration: 0.22s; + +// ============================================================================= +// КОНТЕЙНЕР +// ============================================================================= + +.join-wrap { + padding: var(--space-2); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%); + border-radius: var(--feed-radius); + background: var(--bs-body-bg); + box-shadow: var(--feed-shadow); +} + +// ============================================================================= +// ГОРИЗОНТАЛЬНАЯ ЛЕНТА +// ============================================================================= + +.join-scroller { + position: relative; + overflow-x: auto; + overflow-y: visible; // для инфо-блока + scrollbar-width: none; // Firefox + -ms-overflow-style: none; // IE/Edge + + // Градиентная маска по краям + mask-image: linear-gradient( + to right, + transparent 0%, + black 40px, + black calc(100% - 40px), + transparent 100% + ); + -webkit-mask-image: linear-gradient( + to right, + transparent 0%, + black 40px, + black calc(100% - 40px), + transparent 100% + ); + + &::-webkit-scrollbar { + display: none; // Chrome/Safari + } +} + +.join-rail { + display: flex; + flex-wrap: nowrap; + align-items: flex-start; + justify-content: center; + gap: $join-gap; + padding: $join-padding; + min-width: 100%; +} + +@media (prefers-reduced-motion: reduce) { + .join-scroller { + scroll-behavior: auto !important; + } +} + +// ============================================================================= +// ЭЛЕМЕНТ ЛЕНТЫ +// ============================================================================= + +.join-item { + --base: #{$join-base-size}; + --mult: #{$join-multiplier}; + --size: var(--base); + + position: relative; + display: flex; + flex-direction: column; + align-items: center; + width: var(--size); + flex: 0 0 var(--size); + transition: width $join-transition-duration ease, flex-basis $join-transition-duration ease; + + &:hover, + &:focus-within { + --size: calc(var(--base) * var(--mult)); + z-index: 2; + } + + // Отключение hover при перетаскивании + &.no-hover { + &:hover, + &:focus-within { + --size: var(--base); + z-index: auto; + } + + .join-info { + max-height: 0; + opacity: 0; + transform: translateY(-4px); + } + } +} + +// ============================================================================= +// АВАТАР +// ============================================================================= + +.join-ava { + width: var(--size); + height: var(--size); + border-radius: 999px; + overflow: hidden; + display: grid; + place-items: center; + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + background: var(--bs-tertiary-bg); + transition: width $join-transition-duration ease, + height $join-transition-duration ease, + box-shadow $join-transition-duration ease, + border-color $join-transition-duration ease; + will-change: width, height; + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + background: var(--bs-tertiary-bg); + } + + .bi { + font-size: 1.2rem; + opacity: 0.65; + } + + .join-item:hover &, + .join-item:focus-within & { + box-shadow: var(--feed-shadow-hover); + border-color: color-mix(in srgb, var(--bs-border-color), var(--bs-primary) 30%); + } +} + +// ============================================================================= +// ИНФО-БЛОК +// ============================================================================= + +.join-info { + width: 100%; + overflow: hidden; + max-height: 0; + opacity: 0; + transform: translateY(-4px); + transition: max-height 0.24s ease, opacity 0.18s ease, transform 0.18s ease; + + .join-item:hover &, + .join-item:focus-within & { + max-height: 320px; // с запасом для навыков + opacity: 1; + transform: translateY(0); + } + + .name { + font-weight: 800; + line-height: 1.2; + margin-top: 0.5rem; + text-align: center; + } + + .sub { + font-size: 0.9rem; + color: var(--bs-secondary-color); + text-align: center; + } + + .skills-line { + font-size: 0.9rem; + text-align: center; + margin-top: 0.35rem; + word-break: break-word; + white-space: normal; + + b { + font-weight: 800; + } + } + + .link { + display: inline-block; + margin-top: 0.35rem; + font-size: 0.9rem; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } +} + +// ============================================================================= +// АДАПТИВНОСТЬ +// ============================================================================= + +@media (max-width: 480px) { + .join-item { + --mult: #{$join-multiplier-mobile}; + } +} diff --git a/backend/static/scss_backup_20260115/components/_layout-spacing.scss b/backend/static/scss_backup_20260115/components/_layout-spacing.scss new file mode 100644 index 00000000..47b9c716 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_layout-spacing.scss @@ -0,0 +1,42 @@ +/** + * layout-spacing.scss + * + * Централизованное управление отступами в app-layout + * Все отступы контролируются через CSS переменные в variables.css + * + * ПРИНЦИП: Контейнеры занимают 100% пространства ячейки (без padding). + * Все отступы между колонками управляются ТОЛЬКО через grid gap. + * + * Переменные для настройки (в variables.css): + * --layout-gap: расстояние между sidebar, main, rightbar (по умолчанию 16px) + * --layout-padding-y: вертикальные отступы layout (по умолчанию 24px) + * --layout-padding-x: горизонтальные отступы layout (по умолчанию 0) + * + * Примеры изменения отступов: + * + * Увеличить расстояние между колонками до 24px: + * :root { --layout-gap: var(--space-3); } + * + * Уменьшить расстояние между колонками до 8px: + * :root { --layout-gap: var(--space-1); } + * + * Уменьшить вертикальные отступы до 16px: + * :root { --layout-padding-y: var(--space-2); } + * + * Убрать вертикальные отступы: + * :root { --layout-padding-y: 0; } + */ + +/* ===================================== + APP LAYOUT SPACING +===================================== */ + +// Применяем вертикальные отступы к layout +.app-layout { + padding-top: var(--layout-padding-y); + padding-bottom: var(--layout-padding-y); +} + +// Горизонтальные отступы управляются через container-xxl px-2 px-lg-3 в base.html +// Grid gap между колонками управляется через --layout-gap в base-app.scss +// Колонки (sidebar, main, rightbar) НЕ имеют собственных padding - занимают 100% ячейки diff --git a/backend/static/scss_backup_20260115/components/_list-view.scss b/backend/static/scss_backup_20260115/components/_list-view.scss new file mode 100644 index 00000000..fa44d79f --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_list-view.scss @@ -0,0 +1,148 @@ +// ============================================================================= +// List View Component (SCSS) +// ============================================================================= +// Универсальные стили для списочных представлений (сотрудники, отделы) + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +$list-row-py: var(--space-1); // 8px вертикальные отступы +$list-row-px: var(--space-2); // 16px горизонтальные отступы +$list-gap: var(--space-2); // 16px расстояние внутри строки +$list-avatar-size: 56px; // размер аватара + +// ============================================================================= +// ТОКЕНЫ +// ============================================================================= + +.list-view { + --list-row-py: #{$list-row-py}; + --list-row-px: #{$list-row-px}; + --list-gap: #{$list-gap}; + --list-avatar: #{$list-avatar-size}; +} + +// ============================================================================= +// КОНТЕЙНЕР СПИСКОВ +// ============================================================================= + +.list-view { + background: transparent; + padding: 0; + + // Убираем gap от card-list + &.card-list { + display: block; + gap: 0 !important; + } +} + +// ============================================================================= +// СТРОКИ СПИСКОВ +// ============================================================================= + +.list-view .list-row { + padding: var(--list-row-py) var(--list-row-px) !important; + margin: 0 !important; + border-bottom: 1px solid var(--bs-border-color); + + &:last-of-type { + border-bottom: 0; + } +} + +// ============================================================================= +// ШАПКА СТРОКИ +// ============================================================================= + +.list-view .list-row .card-header { + display: flex; + align-items: center; + gap: var(--list-gap); + min-height: var(--list-avatar); + margin: 0; + padding: 0 !important; +} + +// Шапка с аватаром: grid layout +.list-view .list-row:has(.card-icon) .card-header { + display: grid; + grid-template-columns: var(--list-avatar) 1fr auto; +} + +// ============================================================================= +// АВАТАР +// ============================================================================= + +.list-view .list-row .card-icon { + width: var(--list-avatar); + height: var(--list-avatar); + border-radius: 50%; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--bs-secondary-bg), transparent 30%); + flex: none; + + img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + } + + i { + font-size: 1.5rem; + line-height: 1; + opacity: 0.6; + } +} + +// ============================================================================= +// КОНТЕНТ +// ============================================================================= + +.list-view .list-row .card-meta { + margin: 0; + padding: 0; + min-width: 0; +} + +.list-view .card-title { + line-height: 1.25; +} + +.emp-pos { + color: var(--bs-secondary-color); +} + +.list-view .card-subtitle { + margin-top: var(--space-1); +} + +// ============================================================================= +// ДЕЙСТВИЯ СПРАВА +// ============================================================================= + +.emp-actions { + justify-self: end; +} + +// ============================================================================= +// УБИРАЕМ НЕНУЖНЫЕ ОТСТУПЫ +// ============================================================================= + +.list-view .list-row .card-actions { + padding: 0 !important; +} + +.list-view .list-row .card-body { + padding: 0 !important; + + &:empty { + display: none !important; + } +} diff --git a/backend/static/scss_backup_20260115/components/_logout-modal.scss b/backend/static/scss_backup_20260115/components/_logout-modal.scss new file mode 100644 index 00000000..e55e59a2 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_logout-modal.scss @@ -0,0 +1,182 @@ +// ============================================================================= +// Logout Modal (iOS-style) (SCSS) +// ============================================================================= +// iOS-style модальное окно выхода с анимациями + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +$ios-avatar-size: 64px; +$ios-icon-size: 56px; +$ios-btn-min-height: 48px; +$ios-border-radius: 16px; +$ios-border-radius-desktop: 18px; +$ios-grip-width: 36px; +$ios-grip-height: 4px; + +// ============================================================================= +// OVERLAY +// ============================================================================= + +.ios-overlay { + position: fixed; + inset: 0; + z-index: 1080; + background: color-mix(in srgb, var(--bs-body-bg), #000 35%); + backdrop-filter: blur(10px) saturate(110%); + display: grid; + place-items: end center; // bottom sheet по умолчанию + + @media (min-width: 992px) { + place-items: center; // центрируем на десктопе + } +} + +// ============================================================================= +// SHEET / CARD +// ============================================================================= + +.ios-sheet { + width: 100%; + max-width: 560px; + background: color-mix(in srgb, var(--bs-body-bg), #fff 6%); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%); + border-radius: $ios-border-radius $ios-border-radius 0 0; + box-shadow: 0 12px 48px rgba(0, 0, 0, 0.28); + animation: slideUp 0.28s ease both; + + @media (min-width: 992px) { + border-radius: $ios-border-radius-desktop; + animation: popIn 0.18s ease both; + } +} + +// Грип-бар как в iOS +.ios-grip { + width: $ios-grip-width; + height: $ios-grip-height; + border-radius: 999px; + background: color-mix(in srgb, var(--bs-body-color), transparent 70%); + margin: var(--space-1) auto; +} + +// ============================================================================= +// CONTENT +// ============================================================================= + +.ios-body { + padding: var(--space-2) var(--space-3) var(--space-3); + + @media (min-width: 992px) { + padding: var(--space-3); + } +} + +.ios-title { + margin: 0.25rem 0; + text-align: center; + font-weight: 800; + letter-spacing: 0.2px; +} + +.ios-sub { + text-align: center; + color: var(--bs-secondary-color); + margin: 0 0 1rem; +} + +// ============================================================================= +// AVATAR & ICON +// ============================================================================= + +.ios-ava { + width: $ios-avatar-size; + height: $ios-avatar-size; + border-radius: 999px; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + background: var(--bs-tertiary-bg); + display: grid; + place-items: center; + margin: 0.25rem auto 0.75rem; + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } +} + +.ios-ico { + width: $ios-icon-size; + height: $ios-icon-size; + border-radius: 14px; + background: color-mix(in srgb, var(--bs-danger), #000 12%); + color: #fff; + display: grid; + place-items: center; + margin: 0.25rem auto 0.75rem; +} + +// ============================================================================= +// BUTTONS +// ============================================================================= + +.ios-actions { + display: grid; + gap: 0.6rem; +} + +.ios-btn { + min-height: $ios-btn-min-height; + border-radius: 12px; + font-weight: 700; + + &-danger { + background: var(--bs-danger); + border-color: var(--bs-danger); + color: #fff; + + &:hover { + filter: brightness(1.02); + } + } + + &-ghost { + background: color-mix(in srgb, var(--bs-body-bg), #fff 6%); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%); + color: var(--bs-body-color); + + &:hover { + background: color-mix(in srgb, var(--bs-body-bg), #fff 9%); + } + } +} + +// ============================================================================= +// ANIMATIONS +// ============================================================================= + +@keyframes slideUp { + from { + transform: translateY(16px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes popIn { + from { + transform: scale(0.98); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} diff --git a/backend/static/scss_backup_20260115/components/_modal-overrides.scss b/backend/static/scss_backup_20260115/components/_modal-overrides.scss new file mode 100644 index 00000000..2d8557ce --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_modal-overrides.scss @@ -0,0 +1,29 @@ +// ============================================================================= +// Modal Overrides (SCSS) +// ============================================================================= +// Z-index corrections для модальных окон + +// ============================================================================= +// MODAL Z-INDEX +// ============================================================================= + +.modal { + z-index: 1080 !important; + + &.show { + z-index: 1080 !important; + } + + // Не даём просвечивать через backdrop + .modal-content { + background-color: var(--bs-body-bg); + } +} + +.modal-backdrop { + z-index: 1075 !important; + + &.show { + z-index: 1075 !important; + } +} diff --git a/backend/static/scss_backup_20260115/components/_modals.scss b/backend/static/scss_backup_20260115/components/_modals.scss new file mode 100644 index 00000000..335f0134 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_modals.scss @@ -0,0 +1,40 @@ +// ============================================================================= +// Стили для модальных окон +// ============================================================================= + +// Скругление углов для всех модальных окон +.modal-content { + border-radius: 1rem !important; // Соответствует карточкам Bootstrap (16px) +} + +// Убираем скроллбар у modal-content +.modal-dialog { + .modal-content { + overflow: hidden; // Скрываем скроллбар + } +} + +// Специальные стили для модального окна поста +#postDetailModal { + .modal-dialog { + max-width: 800px !important; + width: 100%; + margin: 1.75rem auto; + } + + .modal-body { + max-height: 85vh; + overflow-y: auto; + padding: 0; + background: var(--bs-body-bg) !important; // Непрозрачный фон + + // Скрываем скроллбар + scrollbar-width: none; // Firefox + -ms-overflow-style: none; // IE и Edge + + // Chrome, Safari, Edge + &::-webkit-scrollbar { + display: none; + } + } +} diff --git a/backend/static/scss_backup_20260115/components/_navbar.scss b/backend/static/scss_backup_20260115/components/_navbar.scss new file mode 100644 index 00000000..e6c582ed --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_navbar.scss @@ -0,0 +1,262 @@ +/** + * navbar.scss + * Стили навбара: меню-тогглер, поиск, уведомления + */ + +/* ===================================== + NAVBAR BASE +===================================== */ + +// Navbar overflow visible для dropdown +.app-navbar { + overflow: visible !important; + + .container-fluid { + overflow: visible !important; + } +} + +/* ===================================== + MOBILE NAVBAR HEIGHT +===================================== */ + +// Увеличенная высота navbar для мобильных устройств +@media (max-width: 767.98px) { + .app-navbar { + min-height: 90px; + + .container-fluid { + min-height: 64px; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + } + } +} + +/* ===================================== + MENU TOGGLE (гамбургер) +===================================== */ + +// Кнопка «гамбургера» — без круга/рамки +.menu-toggle { + padding: 0; + border: 0; + background: transparent; + line-height: 1; + color: #fff; + + &:focus { + outline: none; + box-shadow: none; + } +} + +/* ===================================== + DROPDOWN AVATAR +===================================== */ + +// Спрятать стрелочку у dropdown-аватара +.dropdown-toggle.no-caret::after { + display: none !important; +} + +/* ===================================== + MOBILE SEARCH OVERLAY +===================================== */ + +.mobile-search-overlay { + position: fixed; + top: var(--navbar-h); + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1030; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + + &.active { + opacity: 1; + pointer-events: auto; + } +} + +// Светлая тема - забеливание +[data-bs-theme="light"] .mobile-search-overlay { + background-color: rgba(255, 255, 255, 0.85); +} + +// Темная тема - затемнение +[data-bs-theme="dark"] .mobile-search-overlay { + background-color: rgba(0, 0, 0, 0.7); +} + +/* ===================================== + BOTTOM NAVBAR (только для мобильных) +===================================== */ + +.bottom-navbar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background-color: var(--bs-primary); + border-top: 0.5px solid var(--bs-border-color); + z-index: 1040; + padding: 0; + box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.08); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +// Темная тема +[data-bs-theme="dark"] .bottom-navbar { + background-color: var(--bs-tertiary-bg); + border-top-color: var(--bs-border-color); + box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.3); +} + +.bottom-navbar-container { + display: flex; + justify-content: space-around; + align-items: center; + height: 60px; + padding: 0 8px; + max-width: 100%; +} + +.bottom-nav-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; + height: 100%; + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + border: none; + background: transparent; + padding: 4px 8px; + transition: all 0.2s ease; + position: relative; + cursor: pointer; + + &:hover, + &:focus, + &:active { + color: #fff; + background: rgba(255, 255, 255, 0.1); + outline: none; + } + + i { + font-size: 1.4rem; + margin-bottom: 2px; + line-height: 1; + } +} + +// Темная тема - другой цвет кнопок +[data-bs-theme="dark"] .bottom-nav-item { + color: var(--bs-secondary-color); + + &:hover, + &:focus, + &:active { + color: var(--bs-emphasis-color); + background: var(--bs-secondary-bg); + } +} + +.bottom-nav-label { + font-size: 0.65rem; + font-weight: 500; + text-align: center; + line-height: 1; + margin-top: 2px; +} + +/* ===================================== + BOTTOM NAV BADGE +===================================== */ + +// Иконка с бейджем для уведомлений +.bottom-nav-icon-wrapper { + position: relative; + display: inline-block; +} + +.bottom-nav-badge { + position: absolute; + top: -6px; + right: -10px; + background: var(--bs-danger); + color: #fff; + border-radius: 10px; // Telegram использует фиксированный radius для круглых бэйджей + padding: 0 6px; + font-size: 11px; + font-weight: 600; + min-width: 20px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + text-align: center; + line-height: 1; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); // Более заметная тень + transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275), + background-color 0.2s ease; + z-index: 10; + + // Мигание при новом сообщении + &.pulse { + animation: badge-pulse 0.6s ease-out; + } + + // Серый для замьюченных чатов + &.badge-muted { + background: var(--bs-secondary); + } + + &:empty { + display: none; + } +} + +@keyframes badge-pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.15); + } + 100% { + transform: scale(1); + } +} + +/* ===================================== + MOBILE LAYOUT +===================================== */ + +// Padding для body, чтобы контент не перекрывался нижней панелью +@media (max-width: 767.98px) { + body { + padding-bottom: 60px; + } + + // Скрыть bottom navbar на странице чата - композер заменяет навигацию + .chat-container ~ .bottom-navbar, + .chat-page ~ .bottom-navbar, + body:has(.chat-container) .bottom-navbar, + body:has(.chat-page) .bottom-navbar { + display: none; + } + + // Убрать padding, когда navbar скрыт + body:has(.chat-container), + body:has(.chat-page) { + padding-bottom: 0; + } +} diff --git a/backend/static/scss_backup_20260115/components/_notifications.scss b/backend/static/scss_backup_20260115/components/_notifications.scss new file mode 100644 index 00000000..ce708738 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_notifications.scss @@ -0,0 +1,471 @@ +// ============================================================================= +// Notifications System Styles (SCSS) +// ============================================================================= +// Стили для системы уведомлений: колокольчик, dropdown, toast, страница списка + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +// Колокольчик +$bell-dot-size: 16px; +$bell-dot-font-size: 9px; +$bell-dot-offset-top: -4px; +$bell-dot-offset-right: -6px; + +// Dropdown +$notification-dropdown-width: 360px; +$notification-dropdown-max-height: 500px; +$notification-dropdown-border-radius: 12px; +$notification-dropdown-body-max-height: 300px; + +// Элементы уведомлений +$notification-item-padding: 12px 16px; +$notification-icon-size: 36px; +$notification-unread-badge-size: 8px; + +// Цвета +$notification-danger: #dc3545; +$notification-primary: #0d6efd; +$notification-unread-bg-light: #f0f7ff; +$notification-unread-bg-dark: #1a3a52; + +// Переходы +$notification-transition: background-color 0.2s; + +// ============================================================================= +// КОЛОКОЛЬЧИК В NAVBAR +// ============================================================================= + +.bell-wrapper { + position: relative; + display: inline-block; +} + +.bell-dot { + position: absolute; + top: $bell-dot-offset-top; + right: $bell-dot-offset-right; + min-width: $bell-dot-size; + height: $bell-dot-size; + padding: 0 4px; + background-color: $notification-danger; + color: white; + border-radius: calc($bell-dot-size / 2); + font-size: $bell-dot-font-size; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3); +} + +// ============================================================================= +// DROPDOWN МЕНЮ УВЕДОМЛЕНИЙ +// ============================================================================= + +.notification-dropdown-menu { + position: fixed; + width: $notification-dropdown-width; + max-height: $notification-dropdown-max-height; + background: white; + border-radius: $notification-dropdown-border-radius; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); + z-index: 10000; + overflow: hidden; + display: flex; + flex-direction: column; + + [data-bs-theme="dark"] & { + background: #2b3035; + color: #dee2e6; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + } +} + +// Заголовок +.notification-dropdown-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid #e9ecef; + background: #f8f9fa; + + [data-bs-theme="dark"] & { + background: #1f2327; + border-bottom-color: #3d4349; + } +} + +// Тело с прокруткой +.notification-dropdown-body { + flex: 1; + overflow-y: auto; + max-height: $notification-dropdown-body-max-height; + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; + scrollbar-width: thin; + scrollbar-gutter: stable both-edges; + overscroll-behavior: contain; + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 70%) transparent; + + &:hover { + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 55%) transparent; + } + + // Webkit scrollbar + &::-webkit-scrollbar { + width: 10px; + height: 10px; + } + + &::-webkit-scrollbar-track { + background: transparent; + margin-block: 0; + } + + &::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--bs-body-color), transparent 75%); + border-radius: 999px; + border: 3px solid transparent; + background-clip: padding-box; + } + + &:hover::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--bs-body-color), transparent 55%); + } + + // Темная тема scrollbar + [data-bs-theme="dark"] & { + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 78%) transparent; + + &:hover { + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 60%) transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--bs-body-color), transparent 78%); + } + + &:hover::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--bs-body-color), transparent 60%); + } + } + + // Скрытие scrollbar на тач-устройствах + @media (hover: none), (pointer: coarse) { + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } +} + +// Разделитель +.notification-dropdown-divider { + height: 1px; + background: #e9ecef; + margin: 0; + + [data-bs-theme="dark"] & { + background: #3d4349; + } +} + +// Секция +.notification-dropdown-section { + padding: 8px 0; +} + +// ============================================================================= +// ЭЛЕМЕНТЫ СПИСКА УВЕДОМЛЕНИЙ +// ============================================================================= + +.notification-item { + display: flex; + gap: 12px; + padding: $notification-item-padding; + cursor: pointer; + transition: $notification-transition; + position: relative; + border-bottom: 1px solid #e9ecef; + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: #f8f9fa; + } + + &.unread { + background-color: $notification-unread-bg-light; + + &:hover { + background-color: #e6f2ff; + } + } + + // Темная тема + [data-bs-theme="dark"] & { + border-bottom-color: #3d4349; + + &:hover { + background-color: #343a40; + } + + &.unread { + background-color: $notification-unread-bg-dark; + + &:hover { + background-color: #1e4260; + } + } + } +} + +// Иконка уведомления +.notification-icon { + flex-shrink: 0; + width: $notification-icon-size; + height: $notification-icon-size; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; +} + +// Контент уведомления +.notification-content { + flex: 1; + min-width: 0; +} + +.notification-title { + font-weight: 600; + font-size: 14px; + margin-bottom: 4px; + color: #212529; + + [data-bs-theme="dark"] & { + color: #dee2e6; + } +} + +.notification-message { + font-size: 13px; + color: #6c757d; + margin-bottom: 4px; + + [data-bs-theme="dark"] & { + color: #adb5bd; + } +} + +.notification-time { + font-size: 11px; + color: #adb5bd; + + [data-bs-theme="dark"] & { + color: #6c757d; + } +} + +// Значок непрочитанного +.notification-unread-badge { + flex-shrink: 0; + width: $notification-unread-badge-size; + height: $notification-unread-badge-size; + background-color: $notification-primary; + border-radius: 50%; + margin-left: 8px; + align-self: center; +} + +// ============================================================================= +// ПУСТОЕ СОСТОЯНИЕ +// ============================================================================= + +.notification-empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 48px 24px; + color: #adb5bd; + text-align: center; + + i { + font-size: 48px; + margin-bottom: 12px; + } + + div { + font-size: 14px; + } + + [data-bs-theme="dark"] & { + color: #6c757d; + } +} + +.extra-small { + font-size: 0.7rem; +} + +// ============================================================================= +// TOAST УВЕДОМЛЕНИЯ (FALLBACK) +// ============================================================================= + +.toast-container { + z-index: 9999; +} + +.toast { + min-width: 300px; +} + +// ============================================================================= +// СТРАНИЦА СПИСКА УВЕДОМЛЕНИЙ +// ============================================================================= + +// Карточки уведомлений - прочитанные +.notification-read { + opacity: 0.7; + + .card { + background-color: #f8f9fa; + } + + [data-bs-theme="dark"] & { + opacity: 0.65; + + .card { + background-color: #1f2327 !important; + border-color: #3d4349 !important; + } + } +} + +// Карточки уведомлений - непрочитанные +.notification-unread { + border-left: 4px solid var(--bs-primary); + + .card { + background-color: #ffffff; + } + + [data-bs-theme="dark"] .card { + background-color: #2b3035; + border-color: #3d4349; + } +} + +// Иконки категорий (для страницы списка) +.notification-icon { + width: 48px; + height: 48px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; +} + +// ============================================================================= +// ТЕМНАЯ ТЕМА ДЛЯ СТРАНИЦЫ СПИСКА +// ============================================================================= + +[data-bs-theme="dark"] { + .card { + background-color: #2b3035; + border-color: #3d4349; + color: #dee2e6; + } + + .card-body { + color: #dee2e6; + } + + .text-muted { + color: #adb5bd !important; + } + + // Кнопки + .btn-outline-primary { + color: #6ea8fe; + border-color: #6ea8fe; + + &:hover { + background-color: #0d6efd; + border-color: #0d6efd; + color: #fff; + } + } + + .btn-outline-secondary { + color: #adb5bd; + border-color: #6c757d; + + &:hover { + background-color: #6c757d; + border-color: #6c757d; + color: #fff; + } + } + + .btn-outline-danger { + color: #ea868f; + border-color: #ea868f; + + &:hover { + background-color: #dc3545; + border-color: #dc3545; + color: #fff; + } + } + + // Формы + .form-select, + .form-control { + background-color: #1f2327; + border-color: #3d4349; + color: #dee2e6; + + &:focus { + background-color: #2b3035; + border-color: #6ea8fe; + color: #dee2e6; + } + } + + // Пагинация + .pagination { + .page-link { + background-color: #2b3035; + border-color: #3d4349; + color: #6ea8fe; + + &:hover { + background-color: #343a40; + border-color: #3d4349; + color: #9ec5fe; + } + } + + .page-item.active .page-link { + background-color: #0d6efd; + border-color: #0d6efd; + color: #fff; + } + + .page-item.disabled .page-link { + background-color: #1f2327; + border-color: #3d4349; + color: #6c757d; + } + } +} diff --git a/backend/static/scss_backup_20260115/components/_page-header.scss b/backend/static/scss_backup_20260115/components/_page-header.scss new file mode 100644 index 00000000..467d08cc --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_page-header.scss @@ -0,0 +1,477 @@ +/** + * _page-header.scss + * Универсальная шапка для страниц списков с: + * - Полупрозрачным фоном (sticky positioning) + * - Иконкой и заголовком + * - Счётчиком элементов + * - Переключателем области (Мои/Все) + * - Кнопкой фильтров + * - Кнопкой создания (вытянутая на десктопе, иконка на мобильных) + * - Поиском iOS-стиль + */ + +/* ───────────────────────────────────────────────────────────── + ОСНОВНОЙ КОНТЕЙНЕР ШАПКИ +────────────────────────────────────────────────────────────── */ +.page-header { + position: sticky; + top: var(--navbar-h, 60px); + z-index: 10; + padding: var(--space-2) 0; + margin-bottom: var(--space-3); + + /* Полупрозрачный фон с градиентом */ + background: linear-gradient( + to bottom, + color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 0%, + color-mix(in srgb, var(--bs-body-bg) 85%, transparent) 30%, + color-mix(in srgb, var(--bs-body-bg) 70%, transparent) 50%, + color-mix(in srgb, var(--bs-body-bg) 40%, transparent) 70%, + transparent 100% + ); + + @include transition((background, box-shadow), $transition-fast); + + &::before { + content: ''; + @include absolute-cover(); + backdrop-filter: saturate(180%) $blur-lg; + -webkit-backdrop-filter: saturate(180%) $blur-lg; + mask-image: linear-gradient( + to bottom, + black 0%, + black 30%, + rgba(0, 0, 0, 0.7) 50%, + rgba(0, 0, 0, 0.4) 70%, + transparent 100% + ); + -webkit-mask-image: linear-gradient( + to bottom, + black 0%, + black 30%, + rgba(0, 0, 0, 0.7) 50%, + rgba(0, 0, 0, 0.4) 70%, + transparent 100% + ); + pointer-events: none; + z-index: -1; + } + + > * { + position: relative; + z-index: $z-base; + } + + /* При скролле - более выраженный фон */ + &.scrolled { + background: linear-gradient( + to bottom, + color-mix(in srgb, var(--bs-body-bg) 95%, transparent) 0%, + color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 30%, + color-mix(in srgb, var(--bs-body-bg) 75%, transparent) 50%, + color-mix(in srgb, var(--bs-body-bg) 45%, transparent) 70%, + transparent 100% + ); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + } + + /* Кнопка создания в виде карточки */ + .card { + margin-top: var(--space-2); + width: 100%; + + button.dept-row, + a.dept-row { + cursor: pointer; + transition: background-color 0.2s ease; + width: 100%; + padding: var(--space-1) var(--space-2); + text-align: center; + + .card-title { + font-weight: 700; + color: var(--bs-body-color); + } + + i { + color: var(--bs-primary); + } + + &:hover { + background-color: var(--bs-light); + } + } + } + + /* Адаптивность */ + .d-flex.align-items-center { + flex-wrap: wrap; + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT AVATAR WITH TYPE BADGE +────────────────────────────────────────────────────────────── */ +.chat-avatar-wrapper { + position: relative; + width: 48px; + height: 48px; + flex-shrink: 0; +} + +.chat-avatar { + width: 100%; + height: 100%; + border-radius: 12px; + object-fit: cover; + display: block; + border: 1px solid var(--bs-border-color); +} + +.chat-type-badge { + position: absolute; + bottom: -2px; + right: -2px; + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--bs-primary); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + border: 2px solid var(--bs-body-bg); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + + i { + line-height: 1; + } + + [data-bs-theme="dark"] & { + border-color: var(--bs-body-bg); + } +} + +/* ───────────────────────────────────────────────────────────── + TOP BAR (заголовок + действия) +────────────────────────────────────────────────────────────── */ +.page-header-top { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-2); + width: 100%; +} + +.page-header-title { + display: flex; + align-items: center; + gap: var(--space-2); + flex: 1; + min-width: 0; + + h1 { + font-size: 1.5rem; + font-weight: 700; + margin: 0; + color: var(--bs-body-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + i { + font-size: 1.75rem; + flex-shrink: 0; + } +} + +.page-header-actions { + display: flex; + align-items: center; + gap: var(--space-1); + flex-shrink: 0; +} + +/* ───────────────────────────────────────────────────────────── + ЗАГОЛОВОК И СЧЁТЧИК +────────────────────────────────────────────────────────────── */ +.page-title { + font-weight: 800; + font-size: 1.75rem; + letter-spacing: -0.01em; + line-height: 1.2; + color: var(--bs-body-color); + flex-shrink: 0; +} + +.page-count { + font-size: 0.875rem; + color: var(--bs-secondary-color); + white-space: nowrap; + flex-shrink: 0; + + .count-value { + font-weight: 600; + color: var(--bs-body-color); + } +} + +/* ───────────────────────────────────────────────────────────── + ПЕРЕКЛЮЧАТЕЛЬ ОБЛАСТИ (МОИ/ВСЕ) +────────────────────────────────────────────────────────────── */ +.page-scope-switch { + flex-shrink: 0; + + .btn { + min-width: 60px; + font-size: 0.875rem; + border-radius: 999px; + padding: 0.375rem 0.75rem; + transition: all 0.2s ease; + + &:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + &.active { + background-color: var(--bs-primary); + border-color: var(--bs-primary); + color: white; + font-weight: 600; + } + } +} + +/* ───────────────────────────────────────────────────────────── + КНОПКА ФИЛЬТРОВ +────────────────────────────────────────────────────────────── */ +.page-filter-toggle { + &:hover { + background-color: color-mix(in srgb, var(--bs-primary), transparent 85%); + border-color: var(--bs-primary); + color: var(--bs-primary); + } + + &[aria-expanded="true"] { + background-color: var(--bs-primary); + border-color: var(--bs-primary); + color: white; + } +} + +/* ───────────────────────────────────────────────────────────── + КНОПКА СОЗДАНИЯ +────────────────────────────────────────────────────────────── */ +.page-create-btn { + font-weight: 600; + font-size: 0.875rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; + + &:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + } + + &:active { + transform: translateY(0); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + } +} + +/* ───────────────────────────────────────────────────────────── + ПОИСК +────────────────────────────────────────────────────────────── */ +.page-search { + flex-shrink: 1; + min-width: 200px; + max-width: 400px; + flex-grow: 1; + + .ios-search { + width: 100%; + } +} + +/* ───────────────────────────────────────────────────────────── + БЛОК ФИЛЬТРОВ +────────────────────────────────────────────────────────────── */ +.page-filters-collapse { + margin-top: var(--space-2); + width: 100%; +} + +.page-filters { + border-radius: var(--radius-lg, 14px); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%); + background: var(--bs-body-bg); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + padding: var(--space-3); + animation: slideDown 0.3s ease; + + .btn { + white-space: nowrap; + min-width: fit-content; + } + + .d-flex.gap-2 { + flex-wrap: wrap; + } +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ───────────────────────────────────────────────────────────── + АДАПТИВНОСТЬ +────────────────────────────────────────────────────────────── */ + +/* Планшеты */ +@media (max-width: 991.98px) { + .page-title { + font-size: 1.5rem; + } + + .page-header { + padding: var(--space-2) 0; + } + + .page-search { + flex-basis: 100%; + max-width: none; + margin-top: var(--space-2); + order: 10; + } + + .page-filter-toggle { + order: 11; + margin-top: var(--space-2); + margin-left: auto; + margin-right: 0 !important; + } +} + +@media (max-width: 767.98px) { + .page-header-title { + h1 { + font-size: 1.25rem; + } + + i { + font-size: 1.5rem; + } + } +} + +/* Мобильные */ +@media (max-width: 575.98px) { + .page-header { + margin-bottom: var(--space-2); + padding: var(--space-1) 0; + top: var(--navbar-h, 60px); + } + + .page-title { + font-size: 1.125rem; + font-weight: 700; + } + + .page-count { + font-size: 0.7rem; + } + + .page-scope-switch { + gap: 0.25rem; + + .btn { + min-width: auto; + font-size: 0.7rem; + padding: 0.2rem 0.4rem; + } + } + + .page-filters { + margin-top: 0.5rem; + + .btn { + padding: 0.4rem 0.75rem; + font-size: 0.85rem; + } + } + + .page-search { + margin-top: 0.5rem; + } + + @media (max-width: 480px) { + .page-count { + display: none; + } + + .page-header .card { + margin-top: 0; + width: 100%; + + button.dept-row, + a.dept-row { + padding: var(--space-1) var(--space-1); + font-size: 0.9rem; + } + } + } +} + +/* ───────────────────────────────────────────────────────────── + ТЕМНАЯ ТЕМА +────────────────────────────────────────────────────────────── */ +@media (prefers-color-scheme: dark) { + .page-header { + background: linear-gradient( + to bottom, + color-mix(in srgb, var(--bs-body-bg) 85%, transparent) 0%, + color-mix(in srgb, var(--bs-body-bg) 80%, transparent) 45%, + color-mix(in srgb, var(--bs-body-bg) 65%, transparent) 50%, + color-mix(in srgb, var(--bs-body-bg) 35%, transparent) 70%, + transparent 100% + ); + border-bottom-color: color-mix(in srgb, var(--bs-border-color), transparent 60%); + + &.scrolled { + background: linear-gradient( + to bottom, + color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 0%, + color-mix(in srgb, var(--bs-body-bg) 80%, transparent) 30%, + color-mix(in srgb, var(--bs-body-bg) 72%, transparent) 50%, + color-mix(in srgb, var(--bs-body-bg) 40%, transparent) 70%, + transparent 100% + ); + } + + .card { + button.dept-row:hover, + a.dept-row:hover { + background-color: color-mix(in srgb, var(--bs-body-bg) 95%, white); + } + } + } + + .page-filters { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3); + } +} diff --git a/backend/static/scss_backup_20260115/components/_post-form.scss b/backend/static/scss_backup_20260115/components/_post-form.scss new file mode 100644 index 00000000..a8269c9c --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_post-form.scss @@ -0,0 +1,20 @@ +// ============================================================================= +// Post Form Component (SCSS) +// ============================================================================= +// Стили формы создания/редактирования поста + +// ============================================================================= +// AUTOGROW TEXTAREA +// ============================================================================= + +textarea.autogrow { + overflow: hidden; // без скроллов до достижения max-height + resize: none; // убираем ручной ресайз + min-height: 120px; + max-height: 70vh; // не занимаем весь экран + + // Когда достигли max-height - включаем скролл + &.is-max { + overflow-y: auto; + } +} diff --git a/backend/static/scss_backup_20260115/components/_recipientPicker.scss b/backend/static/scss_backup_20260115/components/_recipientPicker.scss new file mode 100644 index 00000000..4e6ce0fe --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_recipientPicker.scss @@ -0,0 +1,353 @@ +// ============================================================================= +// Recipient Picker (Email-style) Styles (SCSS) +// ============================================================================= +// Компонент выбора получателей в стиле email-клиента + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +$recipient-avatar-size: 32px; +$recipient-avatar-size-mobile: 28px; +$recipient-label-width: 80px; +$recipient-chip-padding: 0.25rem 0.5rem; +$recipient-chip-padding-mobile: 0.2rem 0.4rem; + +// Цвета +$chip-primary-color: var(--bs-primary); +$chip-primary-hover: var(--bs-primary-dark, #0056b3); +$chip-secondary-color: var(--bs-secondary); +$chip-secondary-hover: var(--bs-secondary-dark, #5a6268); + +// ============================================================================= +// ОСНОВНОЙ КОНТЕЙНЕР +// ============================================================================= + +.recipient-picker-email { + font-size: 0.9375rem; +} + +// ============================================================================= +// СТРОКИ ПОЛУЧАТЕЛЕЙ (To/CC/Отделы) +// ============================================================================= + +.recipient-row { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.75rem 0; + border-bottom: 1px solid var(--bs-border-color); + + &:last-child { + border-bottom: none; + } +} + +// ============================================================================= +// МЕТКА (To:/CC:/Отделы:) +// ============================================================================= + +.recipient-label { + min-width: $recipient-label-width; + padding-top: 0.5rem; + color: var(--bs-secondary); + font-weight: 500; + display: flex; + align-items: center; + gap: 0.5rem; + margin: 0; + + i { + font-size: 1.1em; + } +} + +// ============================================================================= +// ПОЛЕ ВВОДА +// ============================================================================= + +.recipient-field { + flex: 1; + display: flex; + align-items: flex-start; + gap: 0.5rem; + flex-wrap: wrap; +} + +// ============================================================================= +// WRAPPER ДЛЯ CHIPS + INPUT +// ============================================================================= + +.recipient-input-wrapper { + flex: 1; + min-width: 300px; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.5rem; + background: var(--bs-body-bg); + border: 1px solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + position: relative; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + + &:focus-within { + border-color: $chip-primary-color; + box-shadow: 0 0 0 0.15rem rgba(var(--bs-primary-rgb), 0.15); + } +} + +// ============================================================================= +// INPUT ВНУТРИ WRAPPER +// ============================================================================= + +.recipient-input { + flex: 1; + min-width: 200px; + border: none; + outline: none; + padding: 0.25rem; + font-size: inherit; + background: transparent; + + &::placeholder { + color: var(--bs-secondary-color); + opacity: 0.7; + } +} + +// ============================================================================= +// CHIPS (ВЫБРАННЫЕ ПОЛЬЗОВАТЕЛИ/ОТДЕЛЫ) +// ============================================================================= + +.recipient-chip { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: $recipient-chip-padding; + background: $chip-primary-color; + color: white; + border-radius: 1rem; + font-size: 0.875rem; + white-space: nowrap; + transition: background-color 0.15s; + + &:hover { + background: $chip-primary-hover; + } + + &.chip-cc { + background: $chip-secondary-color; + + &:hover { + background: $chip-secondary-hover; + } + } +} + +.chip-remove { + background: none; + border: none; + padding: 0; + color: white; + cursor: pointer; + display: flex; + align-items: center; + opacity: 0.8; + transition: opacity 0.15s; + + &:hover { + opacity: 1; + } + + i { + font-size: 1.1em; + line-height: 1; + } +} + +// Chips для отделов +.recipient-chips { + display: inline-flex; + flex-wrap: wrap; + gap: 0.375rem; + align-items: center; +} + +// ============================================================================= +// DROPDOWN С AUTOCOMPLETE +// ============================================================================= + +.recipient-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + margin-top: 0.25rem; + background: white; + border: 1px solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + max-height: 300px; + overflow-y: auto; + z-index: 1050; +} + +.dropdown-item-user { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.625rem 1rem; + cursor: pointer; + transition: background-color 0.15s; + + &:hover { + background-color: var(--bs-light); + } +} + +.dropdown-item-empty { + padding: 0.75rem 1rem; + color: var(--bs-secondary); + text-align: center; + font-size: 0.875rem; +} + +// ============================================================================= +// АВАТАР ПОЛЬЗОВАТЕЛЯ +// ============================================================================= + +.user-avatar-small { + width: $recipient-avatar-size; + height: $recipient-avatar-size; + border-radius: 50%; + background: $chip-primary-color; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 0.75rem; + flex-shrink: 0; + overflow: hidden; +} + +.user-avatar-img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; +} + +.user-avatar-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: $chip-primary-color; + color: white; + font-weight: 600; + font-size: 0.75rem; +} + +// ============================================================================= +// ИНФОРМАЦИЯ О ПОЛЬЗОВАТЕЛЕ +// ============================================================================= + +.user-info { + flex: 1; + min-width: 0; +} + +.user-name { + font-weight: 500; + color: var(--bs-body-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.user-subtitle { + font-size: 0.8125rem; + color: var(--bs-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +// ============================================================================= +// ПОДСКАЗКИ +// ============================================================================= + +.alert-sm { + padding: 0.5rem 0.75rem; + margin-top: 0.75rem; + font-size: 0.875rem; +} + +// ============================================================================= +// АДАПТИВНОСТЬ: ПЛАНШЕТЫ +// ============================================================================= + +@media (max-width: 768px) { + .recipient-row { + flex-direction: column; + gap: 0.5rem; + } + + .recipient-label { + min-width: auto; + padding-top: 0; + } + + .recipient-input-wrapper { + min-width: 100%; + } + + .recipient-input { + min-width: 150px; + } +} + +// ============================================================================= +// АДАПТИВНОСТЬ: МОБИЛЬНЫЕ +// ============================================================================= + +@media (max-width: 576px) { + .recipient-chip { + font-size: 0.8125rem; + padding: $recipient-chip-padding-mobile; + } + + .user-avatar-small { + width: $recipient-avatar-size-mobile; + height: $recipient-avatar-size-mobile; + font-size: 0.7rem; + } + + .dropdown-item-user { + padding: 0.5rem 0.75rem; + } +} + +// ============================================================================= +// ТЕМНАЯ ТЕМА +// ============================================================================= + +@media (prefers-color-scheme: dark) { + .recipient-input-wrapper { + background: var(--bs-dark); + border-color: var(--bs-gray-700); + } + + .recipient-dropdown { + background: var(--bs-dark); + border-color: var(--bs-gray-700); + } + + .dropdown-item-user:hover { + background-color: var(--bs-gray-800); + } +} diff --git a/backend/static/scss_backup_20260115/components/_request-detail.scss b/backend/static/scss_backup_20260115/components/_request-detail.scss new file mode 100644 index 00000000..c749be01 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_request-detail.scss @@ -0,0 +1,225 @@ +// ============================================================================= +// Request Detail Styles (SCSS) +// ============================================================================= +// Стили для страницы детального просмотра заявления + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +$req-detail-icon-size: 48px; +$req-detail-icon-size-mobile: 40px; +$req-detail-padding: 1rem 1.25rem; +$req-detail-padding-mobile: 0.75rem 1rem; +$req-info-label-width: 140px; + +// ============================================================================= +// ЗАГОЛОВОК КАРТОЧКИ +// ============================================================================= + +.request-detail-card .req-detail-header { + padding: $req-detail-padding; + border-bottom: 1px solid var(--bs-border-color); + background: var(--bs-body-bg); +} + +.request-detail-card .req-detail-row { + display: flex; + align-items: flex-start; + gap: 1rem; +} + +.request-detail-card .req-detail-icon { + flex-shrink: 0; + + .card-icon { + width: $req-detail-icon-size; + height: $req-detail-icon-size; + display: flex; + align-items: center; + justify-content: center; + background: var(--bs-tertiary-bg); + border-radius: 0.5rem; + font-size: 1.5rem; + } +} + +.request-detail-card .req-detail-content { + flex: 1; + min-width: 0; + + h1 { + font-size: 1.25rem; + font-weight: 600; + margin: 0; + line-height: 1.3; + } +} + +.request-detail-card .req-detail-actions { + display: flex; + gap: 0.5rem; + flex-shrink: 0; +} + +// ============================================================================= +// ИНФОРМАЦИОННЫЕ БЛОКИ +// ============================================================================= + +.req-info-block { + display: flex; + padding: 0.75rem 0; + border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 50%); + + &:last-child { + border-bottom: none; + padding-bottom: 0; + } + + &:first-child { + padding-top: 0; + } +} + +.req-info-label { + flex: 0 0 $req-info-label-width; + font-size: 0.875rem; + color: var(--bs-secondary-color); + font-weight: 500; +} + +.req-info-value { + flex: 1; + min-width: 0; + font-size: 0.9375rem; +} + +.req-comment-text { + padding: 0.75rem 1rem; + background: var(--bs-tertiary-bg); + border-radius: 0.375rem; + line-height: 1.5; +} + +// ============================================================================= +// СЕКЦИЯ КОММЕНТАРИЕВ +// ============================================================================= + +#comments-section { + position: sticky; + top: 1rem; + display: flex; + flex-direction: column; + max-height: 80vh; + overflow: visible; + + .card-header { + background: var(--bs-body-bg); + padding: 0.75rem 1rem; + font-size: 0.9375rem; + } + + .card-body { + flex: 1; + min-height: 100px; + overflow-y: auto; + overflow-x: visible; + } + + .list-group-item { + padding: 0.75rem 1rem; + border-left: 0; + border-right: 0; + + &:first-child { + border-top: 0; + } + } + + .card-footer { + padding: 0.75rem 1rem; + position: relative; + z-index: 100; + overflow: visible; + flex-shrink: 0; + border-top: 1px solid var(--bs-border-color); + + .message-field { + background: var(--bs-tertiary-bg); + } + } + + .dropdown { + position: static; + } + + .dropdown-menu { + z-index: 2000 !important; + position: fixed !important; + max-height: 350px; + overflow-y: auto; + width: auto !important; + min-width: 300px; + } +} + +// ============================================================================= +// АДАПТИВНОСТЬ: ПЛАНШЕТЫ +// ============================================================================= + +@media (max-width: 992px) { + .request-detail-card .req-detail-row { + flex-wrap: wrap; + } + + .request-detail-card .req-detail-content { + flex: 1 1 calc(100% - 64px); + } + + .request-detail-card .req-detail-actions { + width: 100%; + margin-top: 0.75rem; + } + + #comments-section { + position: static; + } + + .req-info-block { + flex-direction: column; + gap: 0.25rem; + } + + .req-info-label { + flex: none; + font-size: 0.75rem; + } +} + +// ============================================================================= +// АДАПТИВНОСТЬ: МОБИЛЬНЫЕ +// ============================================================================= + +@media (max-width: 576px) { + .request-detail-card .req-detail-header { + padding: $req-detail-padding-mobile; + } + + .request-detail-card .req-detail-icon .card-icon { + width: $req-detail-icon-size-mobile; + height: $req-detail-icon-size-mobile; + font-size: 1.25rem; + } + + .request-detail-card .req-detail-content h1 { + font-size: 1.1rem; + } + + .request-detail-card .req-detail-actions { + flex-direction: column; + + .btn { + width: 100%; + } + } +} diff --git a/backend/static/scss_backup_20260115/components/_request-list.scss b/backend/static/scss_backup_20260115/components/_request-list.scss new file mode 100644 index 00000000..aaee9363 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_request-list.scss @@ -0,0 +1,545 @@ +// ============================================================================= +// Request List Styles (SCSS) +// ============================================================================= +// Табличный вид списка заявлений с адаптивным дизайном + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +// Размеры колонок таблицы +$req-icon-size: 48px; +$req-icon-size-tablet: 42px; +$req-icon-size-mobile: 36px; +$req-icon-size-compact: 32px; + +// Отступы +$req-row-padding: 0.75rem 1rem; +$req-row-padding-mobile: 1rem 0.75rem; +$req-row-padding-compact: 0.625rem; +$req-cell-gap: 0.75rem; +$req-cell-gap-mobile: 0.75rem; + +// Переходы +$req-hover-transition: background-color 0.15s ease; + +// ============================================================================= +// КОНТЕЙНЕР СПИСКА +// ============================================================================= + +#reqList.card { + display: block; + gap: 0; + padding: 0; +} + +// ============================================================================= +// ЗАГОЛОВОК ТАБЛИЦЫ +// ============================================================================= + +#reqList .req-table-header { + position: sticky; + top: 0; + z-index: 10; + background: var(--bs-body-bg); + border-bottom: 2px solid var(--bs-border-color); + + .req-table-row { + padding: 0.625rem 1rem; + } + + h6 { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--bs-secondary-color); + margin: 0; + } + + .req-cell { + align-items: center; + } + + // Выравнивание заголовков + .req-cell-title h6, + .req-cell-author h6 { + text-align: left; + } + + .req-cell-dates h6, + .req-cell-status h6, + .req-cell-created h6, + .req-cell-actions h6 { + text-align: center; + } +} + +// ============================================================================= +// СТРОКА ЗАЯВЛЕНИЯ +// ============================================================================= + +#reqList .req-row { + padding: 0; + margin: 0; + border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 50%); + transition: $req-hover-transition; + + &:hover { + background-color: color-mix(in srgb, var(--bs-primary) 3%, transparent); + } + + &:last-child { + border-bottom: 0; + } +} + +// ============================================================================= +// ТАБЛИЧНАЯ СЕТКА +// ============================================================================= + +#reqList .req-table-row { + display: grid; + grid-template-columns: + $req-icon-size // Иконка + minmax(200px, 2fr) // Заголовок (расширяемая) + minmax(120px, 1fr) // Автор + 100px // Даты + 110px // Статус + 80px // Создано + 80px; // Действия + gap: $req-cell-gap; + align-items: start; + padding: $req-row-padding; +} + +// ============================================================================= +// ЯЧЕЙКИ ТАБЛИЦЫ +// ============================================================================= + +#reqList .req-cell { + min-width: 0; // Для корректного word-wrap + display: flex; + align-items: center; +} + +#reqList .req-cell-content { + width: 100%; + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; + hyphens: auto; + line-height: 1.4; +} + +// ============================================================================= +// СПЕЦИФИЧНЫЕ СТИЛИ ЯЧЕЕК +// ============================================================================= + +#reqList .req-cell-icon { + justify-content: center; + + .card-icon { + width: 38px; + height: 38px; + flex-shrink: 0; + } +} + +#reqList .req-cell-title { + font-size: 0.9rem; + + strong { + display: block; + margin-bottom: 0.125rem; + } + + .badge { + font-size: 0.7rem; + padding: 0.2rem 0.4rem; + } +} + +#reqList .req-cell-author { + font-size: 0.875rem; +} + +#reqList .req-cell-dates { + font-size: 0.8rem; + font-variant-numeric: tabular-nums; + line-height: 1.3; +} + +#reqList .req-cell-status { + justify-content: flex-start; + + .badge { + font-size: 0.7rem; + padding: 0.25rem 0.5rem; + white-space: nowrap; + } +} + +#reqList .req-cell-created { + font-size: 0.75rem; + text-align: center; +} + +#reqList .req-cell-actions { + justify-content: center; + gap: 0.25rem; + + .req-cell-content { + display: flex; + gap: 0.25rem; + justify-content: center; + } +} + +// ============================================================================= +// ФУТЕР С КОММЕНТАРИЯМИ +// ============================================================================= + +#reqList .req-footer { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + margin-left: calc($req-icon-size + $req-cell-gap); + border-top: none; + background: transparent; +} + +#reqList .comments-block { + margin: 0; + padding: 1rem; + margin-left: calc($req-icon-size + $req-cell-gap); +} + +// ============================================================================= +// АДАПТИВНОСТЬ: ПЛАНШЕТЫ +// ============================================================================= + +@media (max-width: 992px) { + #reqList .req-table-header .req-table-row, + #reqList .req-table-row { + grid-template-columns: + $req-icon-size-tablet // Иконка + minmax(150px, 2fr) // Заголовок + minmax(100px, 1fr) // Автор + 90px // Даты + 100px // Статус + 70px; // Создано + } + + // Скрываем колонку действий в заголовке + #reqList .req-table-header .req-cell-actions { + display: none; + } + + // Но кнопки остаются видимыми + #reqList .req-cell-actions { + grid-column: 6; + display: flex !important; + justify-content: flex-end; + } +} + +// ============================================================================= +// АДАПТИВНОСТЬ: МОБИЛЬНЫЕ +// ============================================================================= + +@media (max-width: 768px) { + // Скрываем заголовок таблицы + #reqList .req-table-header { + display: none; + } + + // Переходим на flexbox + #reqList .req-table-row { + display: flex; + flex-direction: row; + flex-wrap: wrap; + padding: $req-row-padding-mobile; + gap: $req-cell-gap-mobile; + align-items: flex-start; + } + + // Сброс для ячеек + #reqList .req-cell { + display: block; + min-width: 0; + } + + #reqList .req-cell-content { + width: 100%; + } + + // Иконка слева сверху + #reqList .req-cell-icon { + flex: 0 0 $req-icon-size-mobile; + padding-top: 0.25rem; + + .card-icon { + width: $req-icon-size-compact; + height: $req-icon-size-compact; + } + } + + // Заголовок занимает оставшуюся ширину + #reqList .req-cell-title { + flex: 1 1 calc(100% - 48px); + font-size: 0.9rem; + + strong { + display: block; + margin-bottom: 0.25rem; + font-size: 0.95rem; + } + + .badge { + display: inline-block; + font-size: 0.7rem; + padding: 0.2rem 0.4rem; + } + + .small { + display: block; + font-size: 0.8rem; + margin-top: 0.25rem; + } + } + + // Остальная информация + #reqList .req-cell-author, + #reqList .req-cell-dates, + #reqList .req-cell-status { + flex: 0 1 auto; + font-size: 0.8rem; + } + + #reqList .req-cell-dates { + white-space: nowrap; + + br { + display: none; + } + } + + #reqList .req-cell-status .badge { + font-size: 0.7rem; + padding: 0.25rem 0.5rem; + white-space: nowrap; + } + + // Скрываем дату создания + #reqList .req-cell-created { + display: none; + } + + // Кнопки действий на отдельной строке + #reqList .req-cell-actions { + display: flex !important; + flex: 1 1 100%; + gap: 0.5rem; + padding-top: 0.75rem; + border-top: 1px solid var(--bs-border-color-translucent); + margin: 0; + + .req-cell-content { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + width: 100%; + } + + .btn { + padding: 0.5rem 0.75rem; + font-size: 0.85rem; + } + } + + // Футер на полную ширину + #reqList .req-footer { + flex: 1 1 100%; + margin-left: 0; + padding: 0.75rem 0; + border-top: 1px solid var(--bs-border-color-translucent); + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + + .btn { + padding: 0.4rem 0.6rem; + font-size: 0.8rem; + } + } + + // Блок комментариев + #reqList .comments-block { + margin-left: 0; + padding: 0.75rem; + } + + // Строка без hover + #reqList .req-row { + padding: 0; + border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 50%); + + &:hover { + background-color: transparent; + } + + &:last-child { + border-bottom: 0; + } + } +} + +// ============================================================================= +// КОМПАКТНЫЙ РЕЖИМ +// ============================================================================= + +@media (max-width: 576px) { + #reqList .req-table-row { + padding: $req-row-padding-compact; + } + + #reqList .req-cell-title .small { + display: none; // Скрываем превью + } + + #reqList .req-cell-dates { + br { + display: none; + } + + .req-cell-content::after { + content: ' — '; + } + } +} + +// ============================================================================= +// AJAX-РЕЖИМ: REQ-ITEM +// ============================================================================= + +.req-item { + border-bottom: 1px solid var(--bs-border-color); + padding: 1rem; + transition: background-color 0.2s; + + &:hover { + background-color: var(--bs-light); + } + + &:last-child { + border-bottom: none; + } +} + +.req-header { + margin-bottom: 0.75rem; +} + +.req-title-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + margin-bottom: 0.5rem; +} + +.req-link { + display: flex; + align-items: center; + gap: 0.5rem; + text-decoration: none; + color: var(--bs-body-color); + font-weight: 500; + flex: 1; + min-width: 0; + + &:hover { + color: var(--bs-primary); + } + + strong { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.req-meta { + display: flex; + flex-wrap: wrap; + gap: 1rem; + font-size: 0.875rem; + color: var(--bs-secondary-color); + + > span { + display: flex; + align-items: center; + gap: 0.25rem; + } +} + +.req-comment { + margin: 0.5rem 0; + padding: 0.5rem; + background-color: var(--bs-light); + border-radius: 0.375rem; + font-size: 0.875rem; + color: var(--bs-secondary-color); +} + +.req-footer { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: center; + padding-top: 0.75rem; + border-top: 1px solid var(--bs-border-color-translucent); + font-size: 0.875rem; +} + +.req-approver, +.req-recipients { + display: flex; + align-items: center; + gap: 0.25rem; + color: var(--bs-secondary-color); +} + +// ============================================================================= +// BADGE СТАТУСЫ +// ============================================================================= + +.badge-warning { + background-color: #ffc107; + color: #000; +} + +.badge-success { + background-color: #198754; + color: #fff; +} + +.badge-danger { + background-color: #dc3545; + color: #fff; +} + +.badge-secondary { + background-color: #6c757d; + color: #fff; +} + +// ============================================================================= +// ВСПОМОГАТЕЛЬНЫЕ +// ============================================================================= + +.load-more-trigger { + visibility: hidden; +} diff --git a/backend/static/scss_backup_20260115/components/_rightbar-calendar.scss b/backend/static/scss_backup_20260115/components/_rightbar-calendar.scss new file mode 100644 index 00000000..6f1ec4fc --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_rightbar-calendar.scss @@ -0,0 +1,331 @@ +/** + * rightbar-calendar.scss + * Стили для компонента правой панели с календарем + * Включает стили для FullCalendar, вертикальной ленты недели и модалов событий + */ + +/* ===================================== + БАЗА КАЛЕНДАРЯ +===================================== */ + +.calendar-wrap { + min-height: 360px; + border-radius: 10px; + overflow: hidden; + background: var(--bs-body-bg); + box-shadow: inset 0 1px 3px color-mix(in srgb, var(--bs-body-color), transparent 92%); + border: 0; +} + +.rightbar-card { + height: 100%; + display: flex; + flex-direction: column; + border-radius: 16px; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%); + background: var(--bs-body-bg); + box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 8px 24px rgba(0,0,0,.08); + min-height: 0; + + .card-header { + flex: 0 0 auto; + background: var(--bs-tertiary-bg); + border-bottom: 1px solid var(--bs-border-color); + padding: var(--space-1) var(--space-2); + } + + .card-body { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding: var(--space-2); + } +} + +// Поддержка многострочного header при длинных названиях отделов +.min-w-0 { + min-width: 0; +} + +/* ===================================== + DESKTOP STICKY LAYOUT +===================================== */ + +// На десктопе rightbar становится sticky с фиксированной высотой +// Высота = 100vh - navbar - 2*отступ (сверху и снизу) +// top = navbar + отступ сверху +// Не перемещается с основным скролом страницы, скролл только внутри .card-body +@media (min-width: 992px) { + .rightbar { + position: sticky; + top: calc(var(--navbar-h) + var(--layout-padding-y, 12px)); + height: calc(100dvh - var(--navbar-h) - 2 * var(--layout-padding-y, 12px)); + overflow: hidden; + // padding управляется через Bootstrap классы в шаблоне (px-0, py-3 и т.д.) + } +} + +/* ===================================== + FULLCALENDAR — КОМПАКТНО +===================================== */ + +.fc { + font-size: 0.7rem; + --fc-border-color: var(--bs-border-color); + --fc-page-bg-color: transparent; + --fc-neutral-bg-color: transparent; + --fc-today-bg-color: color-mix(in srgb, var(--bs-primary), transparent 85%); + --fc-event-bg-color: var(--bs-primary); + --fc-event-border-color: var(--bs-primary); + --fc-event-text-color: var(--bs-body-bg); + + // Убираем границы + .fc-theme-standard .fc-scrollgrid { + border: 0 !important; + } + + .fc-theme-standard td, + .fc-theme-standard th { + border: 0 !important; + } + + // Toolbar + .fc-toolbar { + padding: var(--space-2); + background: var(--bs-tertiary-bg); + border-bottom: 1px solid var(--bs-border-color); + justify-content: space-between; + } + + .fc-toolbar-title { + font-size: 1rem; + font-weight: 600; + color: var(--bs-body-color); + } + + .fc-button { + border-radius: 8px; + font-size: .82rem; + padding: var(--space-1); + background: transparent; + border: none; + color: var(--bs-primary); + + &:hover { + background: rgba(var(--bs-primary-rgb), .1); + } + } + + // Day cells + .fc-daygrid-day-frame { + padding: 2px 3px !important; + } + + .fc-daygrid-day-number { + font-size: .82rem; + width: 100%; + height: 1.6rem; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + color: var(--bs-body-color); + text-decoration: none !important; + } + + .fc-day-today { + background: none !important; + + .fc-daygrid-day-number { + color: var(--bs-primary); + font-weight: 700; + border: 1px solid var(--bs-primary); + border-radius: 50%; + } + } + + .fc-day-other .fc-daygrid-day-number { + opacity: .4; + } + + // Events + .fc-daygrid-day-events { + padding: 0 2px 2px !important; + row-gap: 2px !important; + } + + .fc-daygrid-event-harness { + margin: 0 0 2px 0 !important; + } + + .fc-daygrid-event { + border-radius: 6px; + padding: var(--space-1) !important; + line-height: 1.02 !important; + background: color-mix(in srgb, var(--bs-primary), var(--bs-body-bg) 75%); + border: 1px solid color-mix(in srgb, var(--bs-primary), transparent 60%); + color: var(--bs-body-color); + + .fc-event-time { + display: none !important; + } + + .fc-event-title { + font-size: .62rem !important; + line-height: 1.02 !important; + letter-spacing: -0.2px; + font-weight: 600; + font-family: "Arial Narrow","Roboto Condensed",system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; + display: -webkit-box !important; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden !important; + white-space: normal !important; + text-overflow: ellipsis; + max-height: calc(1.02em * 3); + } + } +} + +/* ===================================== + ВЕРТИКАЛЬНАЯ ЛЕНТА НЕДЕЛИ +===================================== */ + +.week-strip-header { + margin-top: var(--space-2); + display: flex; + align-items: center; + justify-content: space-between; + color: var(--bs-secondary-color); + font-size: .8rem; +} + +.week-vertical { + margin-top: var(--space-1); + display: flex; + flex-direction: column; + gap: var(--space-1); + + .week-row { + display: grid; + grid-template-columns: 56px 1fr; + align-items: start; + gap: var(--space-1); + padding: var(--space-1); + border-radius: 12px; + background: color-mix(in srgb, var(--bs-primary), var(--bs-body-bg) 86%); + border: 1px solid color-mix(in srgb, var(--bs-primary), transparent 70%); + + &:hover { + background: color-mix(in srgb, var(--bs-primary), var(--bs-body-bg) 82%); + } + } + + .content { + .title { + font-size: .8rem; + font-weight: 600; + color: var(--bs-body-color); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + white-space: normal; + } + + .meta { + margin-top: .15rem; + font-size: .72rem; + color: var(--bs-secondary-color); + } + } +} + +.date-badge { + display: grid; + place-items: center; + border-radius: 10px; + padding: .35rem .25rem; + background: color-mix(in srgb, var(--bs-primary), var(--bs-body-bg) 92%); + border: 1px solid color-mix(in srgb, var(--bs-primary), transparent 75%); + text-align: center; + line-height: 1.05; + + .dow { + font-size: .7rem; + font-weight: 700; + color: var(--bs-primary); + text-transform: uppercase; + } + + .md { + font-size: .74rem; + font-weight: 700; + color: var(--bs-body-color); + } +} + +/* ===================================== + OFFCANVAS (МОБАЙЛ) +===================================== */ + +#rightbarOffcanvas { + height: 100dvh; + height: 100svh; + + .offcanvas-body { + display: flex; + flex-direction: column; + gap: var(--space-1); + height: calc(100dvh - var(--rb-offcanvas-head, 56px)); + height: calc(100svh - var(--rb-offcanvas-head, 56px)); + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; + } + + .calendar-wrap { + flex: 0 0 auto; + } + + .week-vertical { + flex: 0 0 auto; + } +} + +/* ===================================== + COLOR PICKER (PALETTE) +===================================== */ + +.color-swatch { + width: 26px; + height: 26px; + border: 1px solid var(--bs-border-color); + border-radius: 9999px; + padding: 0; + cursor: pointer; + background: transparent; + + &.active { + outline: 2px solid var(--bs-primary); + outline-offset: 2px; + } +} + +/* ===================================== + EVENT DETAILS MODAL +===================================== */ + +.color-dot { + width: 12px; + height: 12px; + border-radius: 9999px; + display: inline-block; + vertical-align: middle; + margin-right: 6px; + border: 1px solid #00000022; +} diff --git a/backend/static/scss_backup_20260115/components/_sidebar.scss b/backend/static/scss_backup_20260115/components/_sidebar.scss new file mode 100644 index 00000000..2287c10e --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_sidebar.scss @@ -0,0 +1,198 @@ +/** + * _sidebar.scss + * Стили левого сайдбара: навигация, активные элементы, переключатель темы + */ + +/* ───────────────────────────────────────────────────────────── + SIDEBAR LAYOUT +────────────────────────────────────────────────────────────── */ +/* Offcanvas остаётся как есть (липкий ≥ lg) */ +#sidebar { + --bs-offcanvas-border-color: transparent; + --bs-offcanvas-border-width: 0; + --bs-offcanvas-border-radius: 0; + border: 0 !important; + border-radius: 0 !important; + box-shadow: none !important; + background: var(--bs-body-bg); +} + +/* На десктопе sidebar становится sticky с фиксированной высотой + Высота = 100vh - navbar - 2*отступ (сверху и снизу) + top = navbar + отступ сверху + Не перемещается с основным скролом страницы, имеет свой внутренний скролл */ +@media (min-width: 992px) { + #sidebar.offcanvas-lg { + position: sticky; + top: calc(var(--navbar-h, 60px) + var(--layout-padding-y, 12px)); + height: calc(100dvh - var(--navbar-h, 60px) - 2 * var(--layout-padding-y, 12px)); + align-self: start; + overflow: auto; + border-right: .5px solid var(--bs-border-color); + transform: none !important; + visibility: visible !important; + } +} + +/* ───────────────────────────────────────────────────────────── + NAVIGATION SECTIONS +────────────────────────────────────────────────────────────── */ +.side-section { + margin: var(--space-1) 0 var(--space-2); +} + +.side-hd { + font-size: .78rem; + font-weight: 800; + letter-spacing: .03em; + text-transform: uppercase; + color: var(--bs-secondary-color); + padding: var(--space-1); + margin: 0 0 var(--space-1) var(--space-1); +} + +.side-nav { + display: flex; + flex-direction: column; + gap: .15rem; +} + +/* ───────────────────────────────────────────────────────────── + NAV LINKS +────────────────────────────────────────────────────────────── */ +.side-nav { + .nav-link { + position: relative; + display: flex; + align-items: center; + gap: .6rem; + padding: .48rem .55rem; + border-radius: $radius-md; + color: var(--bs-body-color); + text-decoration: none; + border: 0; + background: transparent !important; + line-height: 1.15; + @include transition((background, color, transform, box-shadow), $transition-fast); + outline: none; + + /* мягкий ховер */ + &:hover { + color: var(--bs-primary); + background: color-mix(in srgb, var(--bs-primary) 10%, transparent); + } + + /* видимая клавиатурная фокусировка */ + &:focus-visible { + @include focus-ring(); + } + + /* активная «пилюля» + тонкий индикатор слева */ + &.active { + color: var(--bs-primary); + font-weight: 800; + background: color-mix(in srgb, var(--bs-primary) 16%, transparent); + + &::before { + content: ""; + position: absolute; + left: -6px; + top: 8px; + bottom: 8px; + width: 3px; + border-radius: 2px; + background: var(--bs-primary); + } + } + + /* «отключённый» пункт выглядит реально disabled */ + &.disabled { + opacity: .5; + pointer-events: none; + } + } +} + +/* ───────────────────────────────────────────────────────────── + ICONS & BADGES +────────────────────────────────────────────────────────────── */ +.side-icon { + width: 1.35rem; + height: 1.35rem; + display: inline-grid; + place-items: center; + flex: 0 0 auto; + + > i { + font-size: 1.1rem; + line-height: 1; + } +} + +.icon-wrap { + position: relative; + display: inline-grid; + place-items: center; +} + +.nav-unread { + position: absolute; + top: -4px; + right: -8px; + min-width: 16px; + height: 16px; + padding: 0 var(--space-1); + border-radius: 9999px; + font-size: 10px; + line-height: 16px; + font-weight: 700; + background: var(--bs-danger); + color: #fff; + border: 1px solid rgba(255, 255, 255, .6); + + [data-bs-theme="dark"] & { + border-color: rgba(0, 0, 0, .25); + } +} + +/* ───────────────────────────────────────────────────────────── + THEME SWITCHER BUTTON +────────────────────────────────────────────────────────────── */ +.btn-theme { + display: inline-flex; + align-items: center; + gap: .5rem; + min-height: 36px; + padding: .35rem .75rem; + border-radius: 999px; + background: var(--bs-tertiary-bg); + color: var(--bs-body-color); + border: 1px solid var(--bs-border-color); + transition: transform .08s ease, background-color .25s ease, border-color .25s ease; + + &:hover { + transform: translateY(-1px); + background: color-mix(in srgb, var(--bs-tertiary-bg) 90%, var(--bs-primary) 10%); + border-color: color-mix(in srgb, var(--bs-border-color), var(--bs-primary) 30%); + } + + &:focus-visible { + outline: 0; + box-shadow: 0 0 0 2px rgba(var(--bs-primary-rgb), .25); + } + + .bi { + font-size: 1rem; + line-height: 1; + } + + .mode-label { + font-weight: 700; + font-size: .85rem; + opacity: .9; + + @media (max-width: 576px) { + display: none; + } + } +} diff --git a/backend/static/scss_backup_20260115/components/_spacing-utils.scss b/backend/static/scss_backup_20260115/components/_spacing-utils.scss new file mode 100644 index 00000000..26b83f55 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_spacing-utils.scss @@ -0,0 +1,136 @@ +/** + * spacing-utils.scss + * Утилиты для точного контроля отступов + * + * Использование CSS переменных из variables.css для унифицированных отступов + */ + +/* ===================================== + GAP UTILITIES +===================================== */ + +// Gap для flex/grid контейнеров +.gap-component { + gap: var(--gap-component); // 16px - стандартный gap между элементами +} + +.gap-section { + gap: var(--gap-section); // 24px - gap между секциями +} + +.gap-1 { + gap: var(--space-1); // 8px +} + +.gap-2 { + gap: var(--space-2); // 16px +} + +.gap-3 { + gap: var(--space-3); // 24px +} + +.gap-4 { + gap: var(--space-4); // 32px +} + +/* ===================================== + MARGIN UTILITIES +===================================== */ + +// Margin bottom для секций и блоков +.mb-section { + margin-bottom: var(--margin-section); // 24px - между секциями +} + +.mb-block { + margin-bottom: var(--margin-block); // 32px - между блоками +} + +// Margin на всех сторонах +.my-section { + margin-block: var(--margin-section); // 24px сверху/снизу +} + +.my-block { + margin-block: var(--margin-block); // 32px сверху/снизу +} + +/* ===================================== + PADDING UTILITIES +===================================== */ + +// Padding для карточек +.p-card { + padding: var(--card-padding); // 16px со всех сторон +} + +.p-card-sm { + padding: var(--card-padding-sm); // 8px со всех сторон +} + +.p-card-lg { + padding: var(--card-padding-lg); // 24px со всех сторон +} + +// Padding по осям +.px-card { + padding-inline: var(--card-padding); // 16px лево/право +} + +.py-card { + padding-block: var(--card-padding); // 16px верх/низ +} + +.px-card-sm { + padding-inline: var(--card-padding-sm); // 8px лево/право +} + +.py-card-sm { + padding-block: var(--card-padding-sm); // 8px верх/низ +} + +.px-card-lg { + padding-inline: var(--card-padding-lg); // 24px лево/право +} + +.py-card-lg { + padding-block: var(--card-padding-lg); // 24px верх/низ +} + +/* ===================================== + RESPONSIVE UTILITIES +===================================== */ + +// Адаптивный gap для мобильных +@media (max-width: 576px) { + .gap-component-mobile { + gap: var(--space-1); // 8px на мобильных + } + + .p-card-mobile { + padding: var(--card-padding-sm); // 8px на мобильных + } +} + +/* ===================================== + КОМБИНИРОВАННЫЕ КЛАССЫ +===================================== */ + +// Стандартная секция с отступами +.section-spacing { + margin-bottom: var(--margin-section); // 24px снизу + padding: var(--card-padding); // 16px внутри +} + +// Компактная секция +.section-spacing-sm { + margin-bottom: var(--space-2); // 16px снизу + padding: var(--card-padding-sm); // 8px внутри +} + +// Просторная секция +.section-spacing-lg { + margin-bottom: var(--margin-block); // 32px снизу + padding: var(--card-padding-lg); // 24px внутри +} diff --git a/backend/static/scss_backup_20260115/components/_team-wheel.scss b/backend/static/scss_backup_20260115/components/_team-wheel.scss new file mode 100644 index 00000000..702513b1 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/_team-wheel.scss @@ -0,0 +1,210 @@ +// ============================================================================= +// Team Wheel Component (SCSS) +// ============================================================================= +// Круговой прокручиваемый виджет сотрудников + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +$wheel-size: 200px; +$ava-size: 52px; +$arc-width: 4px; + +// ============================================================================= +// CSS ПЕРЕМЕННЫЕ +// ============================================================================= + +:root { + --wheel-size: #{$wheel-size}; + --ava-size: #{$ava-size}; + --ring-bg: radial-gradient( + ellipse at center, + var(--bs-body-bg), + color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondary-bg, #f7f7f9) 15%) + ); + --ring-border: var(--bs-border-color, rgba(0, 0, 0, 0.125)); + --shadow-inset: 0 8px 24px rgba(0, 0, 0, 0.06) inset; + --shadow-out: 0 4px 12px rgba(0, 0, 0, 0.05); + --arc-width: #{$arc-width}; + --arc-track: color-mix(in srgb, var(--bs-secondary-color) 30%, transparent); + --arc-prog: var(--bs-primary); +} + +// ============================================================================= +// TEAM WRAP & WHEEL +// ============================================================================= + +.team-wrap { + width: var(--wheel-size); + display: flex; + flex-direction: column; + align-items: center; + margin-inline: auto; +} + +.team-wheel { + position: relative; + width: var(--wheel-size); + height: var(--wheel-size); + border-radius: 50%; + overflow: hidden; + background: var(--ring-bg); + border: 1px solid var(--ring-border); + box-shadow: var(--shadow-inset); + user-select: none; + touch-action: none; + display: flex; + align-items: center; + justify-content: center; +} + +// ============================================================================= +// WHEEL LAYOUT +// ============================================================================= + +.wheel-plane { + position: absolute; + inset: 0; +} + +.wheel-columns { + position: absolute; + inset: 10px var(--space-1); + display: flex; + gap: var(--space-1); + justify-content: space-between; + align-items: flex-start; + will-change: transform; +} + +.wheel-col { + width: calc(25% - 6px); + display: flex; + flex-direction: column; + gap: var(--space-1); + align-items: center; + + &.col-2 { + margin-top: calc(var(--ava-size) * 0.5); + } + + &.col-3 { + margin-top: calc(var(--ava-size) * 1); + } + + &.col-4 { + margin-top: calc(var(--ava-size) * 1.5); + } +} + +// ============================================================================= +// WHEEL ITEMS +// ============================================================================= + +.wheel-item { + background: transparent; + border: 0; + padding: 0; + width: var(--ava-size); + height: var(--ava-size); + border-radius: 50%; + cursor: pointer; + box-shadow: var(--shadow-out); + + .ava { + width: 100%; + height: 100%; + border-radius: 50%; + overflow: hidden; + display: grid; + place-items: center; + background: var(--bs-tertiary-bg); + border: 1px solid var(--bs-border-color); + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + .bi { + font-size: 1.2rem; + opacity: 0.5; + } + } + + &:active { + transform: scale(0.96); + } +} + +// ============================================================================= +// PROGRESS RING +// ============================================================================= + +.progress-ring { + position: absolute; + inset: -1px; + transform: rotate(-90deg); + pointer-events: none; +} + +.progress-ring__circle { + stroke: var(--arc-track); + fill: none; + stroke-width: var(--arc-width); + stroke-linecap: round; +} + +.progress-ring__prog { + stroke: var(--arc-prog); + fill: none; + stroke-width: var(--arc-width); + stroke-linecap: round; + transition: stroke-dashoffset 0.35s; +} + +// ============================================================================= +// CONTROLS +// ============================================================================= + +.team-controls { + margin-top: var(--space-2); + display: flex; + gap: var(--space-1); + align-items: center; +} + +.team-nav { + padding: var(--space-1); + border: 1px solid var(--bs-border-color); + background: var(--bs-body-bg); + border-radius: 8px; + + &:active:not(:disabled) { + transform: scale(0.96); + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } +} + +.team-dots { + display: flex; + gap: 6px; + align-items: center; +} + +.team-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: color-mix(in srgb, var(--bs-secondary-color), transparent 70%); + + &.active { + background: var(--bs-primary); + } +} diff --git a/backend/static/scss_backup_20260115/components/chat/_chat-date-groups.scss b/backend/static/scss_backup_20260115/components/chat/_chat-date-groups.scss new file mode 100644 index 00000000..41ad1bcc --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_chat-date-groups.scss @@ -0,0 +1,183 @@ +/** + * _chat-date-groups.scss + * Стили для группировки сообщений по датам + * Основано на Telegram Web архитектуре + */ + +/* ───────────────────────────────────────────────────────────── + MESSAGE DATE GROUP +────────────────────────────────────────────────────────────── */ +.message-date-group { + position: relative; + display: flex; + flex-direction: column; + width: 100%; +} + +/* ───────────────────────────────────────────────────────────── + STICKY DATE HEADER +────────────────────────────────────────────────────────────── */ +.sticky-date { + position: sticky; + top: 56px; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + padding: 12px 16px; + margin: 0; + font-size: 0.875rem; + font-weight: 500; + color: var(--bs-secondary); + text-align: center; + background-color: transparent; + pointer-events: none; + transition: opacity 0.15s ease, background-color 0.2s ease; + + span { + display: inline-block; + padding: 6px 12px; + border-radius: 12px; + background-color: var(--bs-light); + pointer-events: all; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease; + } + + &.interactive span { + cursor: pointer; + user-select: none; + + &:hover { + background-color: var(--bs-primary); + color: white; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + transform: translateY(-1px); + } + + &:active { + transform: translateY(0); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + } + + &:focus-visible { + outline: 2px solid var(--bs-primary); + outline-offset: 2px; + } + } + + &.is-sticky span { + background-color: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + } +} + +/* ───────────────────────────────────────────────────────────── + MESSAGES CONTAINER +────────────────────────────────────────────────────────────── */ +.messages-container { + display: flex; + flex-direction: column; + width: 100%; + gap: 0; +} + +/* ───────────────────────────────────────────────────────────── + LOADING STATE +────────────────────────────────────────────────────────────── */ +.message-date-group.loading .sticky-date span { + animation: pulse 1.5s ease-in-out infinite; + background: linear-gradient( + 90deg, + var(--bs-light) 25%, + rgba(0, 0, 0, 0.05) 50%, + var(--bs-light) 75% + ); + background-size: 200% 100%; +} + +@keyframes pulse { + 0%, 100% { + background-position: 200% 0; + } + 50% { + background-position: 0 0; + } +} + +/* ───────────────────────────────────────────────────────────── + RESPONSIVE DESIGN +────────────────────────────────────────────────────────────── */ +@media (max-width: 576px) { + .sticky-date { + top: 48px; + padding: 10px 12px; + font-size: 0.8125rem; + + span { + padding: 5px 10px; + border-radius: 10px; + } + } +} + +@media (min-width: 577px) and (max-width: 992px) { + .sticky-date { + top: 52px; + } +} + +/* ───────────────────────────────────────────────────────────── + DARK MODE SUPPORT +────────────────────────────────────────────────────────────── */ +@media (prefers-color-scheme: dark) { + .sticky-date { + color: var(--bs-light); + + span { + background-color: rgba(52, 58, 64, 0.95); + color: var(--bs-light); + } + + &.interactive span:hover { + background-color: var(--bs-primary); + color: white; + } + + &.is-sticky span { + background-color: rgba(52, 58, 64, 0.98); + backdrop-filter: blur(10px); + } + } +} + +/* ───────────────────────────────────────────────────────────── + ACCESSIBILITY +────────────────────────────────────────────────────────────── */ +@media (prefers-contrast: high) { + .sticky-date span { + border: 1px solid currentColor; + } +} + +@media (prefers-reduced-motion: reduce) { + .sticky-date, + .sticky-date span { + transition: none; + } +} + +/* ───────────────────────────────────────────────────────────── + PRINT STYLES +────────────────────────────────────────────────────────────── */ +@media print { + .sticky-date { + position: static; + page-break-inside: avoid; + + &.interactive span { + cursor: default; + } + } +} diff --git a/backend/static/scss_backup_20260115/components/chat/_chat-detail.scss b/backend/static/scss_backup_20260115/components/chat/_chat-detail.scss new file mode 100644 index 00000000..4de8eed3 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_chat-detail.scss @@ -0,0 +1,901 @@ +/** + * chat-detail.scss + * Стили детального чата: scroll, bubbles, composer + * Layout вынесен в layout/_chat-layout.scss + */ + +/* ───────────────────────────────────────────────────────────── + CHAT PAGE CONTAINER +────────────────────────────────────────────────────────────── */ +.chat-page { + position: relative; + display: flex; + flex-direction: column; + // Учитываем высоту navbar сверху + height: calc(100vh - var(--navbar-h, 60px)); + overflow: hidden; +} + +.chat-header { + flex-shrink: 0; + z-index: $z-dropdown - 1; + background: var(--bs-body-bg); + border-bottom: 1px solid var(--bs-border-color); + padding: var(--space-3) var(--space-4); + + &-content { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + } + + &-title { + display: flex; + align-items: center; + gap: var(--space-3); + flex: 1; + min-width: 0; + + h1 { + font-size: 1.25rem; + font-weight: 600; + margin: 0; + color: var(--bs-body-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + i { + font-size: 1.5rem; + color: var(--bs-primary); + flex-shrink: 0; + } + } + + &-actions { + display: flex; + align-items: center; + gap: var(--space-2); + flex-shrink: 0; + } +} + +// Mobile стили для header +@media (max-width: 767.98px) { + .chat-header { + padding: var(--space-2) var(--space-3); + + &-title { + gap: var(--space-2); + + h1 { + font-size: 1.125rem; + } + + i { + font-size: 1.25rem; + } + } + + &-actions { + gap: var(--space-1); + } + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT CONTAINER (основной контейнер чата) +────────────────────────────────────────────────────────────── */ +.chat-container { + // Естественный размер контента, не растягиваем + flex: 1 1 auto; + + // Вертикальный flex для: alerts → messages → typing + display: flex; + flex-direction: column; + + // Критично для работы overflow в дочерних элементах + min-height: 0; + + // Предотвращает переполнение, scroll делегируется .chat-messages-scroll + overflow: hidden; + + // Для позиционирования scroll-to-bottom кнопки + position: relative; + + // Опциональный background для тестирования layout + // background: var(--bs-body-bg); + + // Alerts внутри контейнера не должны сжиматься + > .alert { + flex-shrink: 0; + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT MESSAGES SCROLL (основной контейнер сообщений) +────────────────────────────────────────────────────────────── */ +.chat-messages-scroll { + flex: 1 0 0; + overflow-y: auto; + overflow-x: hidden; + padding: var(--space-3); + min-height: 0; + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; + scrollbar-width: thin; + scrollbar-gutter: stable; + overscroll-behavior: contain; + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 70%) transparent; + + &:hover { + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 55%) transparent; + } + + // Кастомный скроллбар для WebKit + &::-webkit-scrollbar { + width: 8px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--bs-body-color), transparent 75%); + border-radius: 4px; + } + + &:hover::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--bs-body-color), transparent 55%); + } +} + +// Алиас для совместимости +.chat-scroll { + @extend .chat-messages-scroll; +} + +.chat-scroll { + @include custom-scrollbar(color-mix(in srgb, var(--bs-body-color), transparent 75%)); + + &:hover { + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 55%) transparent; + } +} + +[data-bs-theme="dark"] .chat-scroll { + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 78%) transparent; + + &:hover { + scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 60%) transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--bs-body-color), transparent 78%); + } + + &:hover::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--bs-body-color), transparent 60%); + } +} + +@media (hover: none), (pointer: coarse) { + .chat-scroll { + scrollbar-width: none; + } + + .chat-scroll::-webkit-scrollbar { + display: none; + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT COMPOSER WRAPPER & COMPOSER (Modern Messenger Style) +────────────────────────────────────────────────────────────── */ +// TODO: Проблема с пустым пространством под композером на широких экранах +// При изменении ширины окна высота navbar меняется, но calc(100vh - var(--navbar-h)) +// не всегда корректно работает. Нужно найти более надежное решение для прижатия +// композера к низу страницы независимо от размера viewport. +.chat-composer-wrapper { + flex-shrink: 0; + margin-top: auto; // Прижимаем к низу chat-page + position: relative; + z-index: $z-base; + background: var(--bs-body-bg); + border-top: 1px solid var(--bs-border-color); + padding: var(--space-2); + padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom)); +} + +// Mobile стили для composer - фиксируем внизу экрана (замена bottom navbar) +@media (max-width: 767.98px) { + .chat-composer-wrapper { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: var(--space-2) var(--space-2); + padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom)); + border-top: 1px solid var(--bs-border-color); + background: var(--bs-body-bg); + z-index: 1020; // Над контентом, но под модалами + } + + // Уменьшенный padding для мобильных + компенсация высоты фиксированного composer + .chat-messages-scroll { + padding: var(--space-2); + padding-bottom: calc(70px + env(safe-area-inset-bottom)); // Высота composer + } +} + +.composer-wrap { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.message-field { + background: color-mix(in srgb, var(--bs-body-bg) 96%, transparent); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%); +} + +.message-input { + max-height: 180px; +} + +// Стили кнопок наследуются из custom-bootstrap.scss (glassmorphism) +.btn-send { + box-shadow: 0 4px 16px rgba(var(--bs-primary-rgb), 0.35); +} + +.message-emoji .dropdown-menu { + background: var(--bs-body-bg); +} + +/* Превью вложений */ +.attachment-preview { + padding: var(--space-1) 0; + max-height: 200px; + overflow-y: auto; + max-width: 100%; + width: 100%; +} + +.attachment-item { + background: var(--bs-secondary-bg); + @include transition(background-color, $transition-fast); + max-width: 100%; // Ограничиваем ширину контейнера +} + +/* Обрезка текста в форме загрузки файлов */ +.attachment-item .flex-grow-1 { + min-width: 0; // Важно для flex-элементов с text-truncate внутри + overflow: hidden; // Предотвращаем выход контента за границы +} + +.attachment-item .text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} + +/* Вложения в сообщениях */ +.bubble .attachment-item { + background: transparent; + padding: 0; + margin-bottom: 8px; + + // Медиа-элементы (изображения и видео) - минимальные отступы + &.attachment-item--media { + margin-bottom: 0; + + a { + display: block; + padding: 0; + line-height: 0; + } + } +} + +// Стили только для ссылок на файлы (документы) +.bubble .attachment-item:not(.attachment-item--media) a { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + border-radius: $radius-sm; + text-decoration: none; + border: none; + @include transition-all($transition-fast); + max-width: 100%; + word-break: break-word; +} + +// Attachment links in regular messages (other person) +.bubble-other .attachment-item a { + background: rgba(0, 0, 0, 0.05); + color: inherit; +} + +.bubble-other .attachment-item a:hover { + background: rgba(0, 0, 0, 0.08); +} + +// Attachment links in own messages +.bubble-me .attachment-item a { + background: rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 0.95); +} + +.bubble-me .attachment-item a:hover { + background: rgba(255, 255, 255, 0.25); +} + +// Icons in attachments +.bubble .attachment-item a i { + font-size: 1.1rem; + flex-shrink: 0; +} + +.bubble-me .attachment-item a i { + color: rgba(255, 255, 255, 0.9); +} + +// Text truncation for file names +.bubble .attachment-item a span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; // Important for flex items +} + +.message-attachments video, +.message-attachments img, +.message-attachments audio { + max-width: 100%; + width: auto; + border-radius: $radius-sm; +} + +// TODO: Исследовать почему у медиа остаются большие отступы +// Возможные причины: кэш браузера, конфликты с Bootstrap, line-height на родителях +// Попробовать: добавить !important, проверить в DevTools computed styles +// Убираем верхний отступ если вложение - только медиа без текста +.message-attachments:has(.attachment-item--media):not(:has(.attachment-item:not(.attachment-item--media))) { + margin-top: 0.25rem; // Уменьшенный отступ для медиа (вместо mt-2 = 0.5rem) +} + +.chat-media { + display: block; + width: auto; + max-width: min(640px, 85vw); + max-height: 520px; + border-radius: $radius-md; + background: rgba(0, 0, 0, 0.08); + content-visibility: auto; + vertical-align: top; +} + +// Telegram approach: изображения с aspect-ratio занимают полную ширину контейнера +// Это позволяет браузеру резервировать место до загрузки → нет скачка скролла +.chat-media--image[style*="aspect-ratio"] { + width: 100% !important; + height: auto !important; +} + +// Убираем этот конфликтующий селектор - заменяем на более специфичный +.message-attachments .attachment-item:not(.attachment-item--media) > a, +.message-attachments .attachment-item:not(.attachment-item--media) > video, +.message-attachments .attachment-item:not(.attachment-item--media) > img { + display: inline-flex; + max-width: 100%; +} + +.chat-media--video { + background: #000; + object-fit: contain; +} + +.chat-media--image { + object-fit: cover; +} + +.message-pending .chat-media { + min-width: 90px; + min-height: 64px; +} + +.message-pending .bubble { + position: relative; + opacity: 0.8; +} + +.message-pending .message-status { + color: rgba(255, 255, 255, 0.8); +} + +/* Состояния сообщений */ +.message-failed { + opacity: 0.6; + + .bubble { + border: 1px solid var(--bs-danger); + background: color-mix(in srgb, var(--bs-danger) 10%, transparent); + } +} + +.message-highlighted { + animation: messageHighlight 1s ease-in-out; + + .bubble { + background: color-mix(in srgb, var(--bs-warning) 20%, var(--bs-secondary-bg)) !important; + } +} + +@keyframes messageHighlight { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.02); + } +} + +/* Режим выделения сообщений */ +.selection-mode { + .msg { + cursor: pointer; + transition: background-color $transition-fast; + + &:hover { + background: var(--bs-tertiary-bg); + } + + &.selected { + background: color-mix(in srgb, var(--bs-primary) 10%, transparent); + } + } + + .msg-select-checkbox { + position: absolute; + left: var(--space-1); + top: 50%; + transform: translateY(-50%); + width: 24px; + height: 24px; + border: 2px solid var(--bs-border-color); + @include radius(50%); + background: var(--bs-body-bg); + cursor: pointer; + transition: all $transition-fast; + @include flex-center(); + z-index: 2; + + &::after { + content: '\f26b'; // Bootstrap icon checkmark + font-family: 'bootstrap-icons'; + color: #fff; + font-size: 12px; + opacity: 0; + transform: scale(0); + transition: all $transition-fast; + } + + &.selected { + background: var(--bs-primary); + border-color: var(--bs-primary); + + &::after { + opacity: 1; + transform: scale(1); + } + } + } +} + +.bubble-other .message-status { + color: rgba(0, 0, 0, 0.5); +} + +.message-attachments .text-secondary { + color: inherit; + opacity: 0.7; +} + +.bubble-me .message-attachments .text-secondary { + color: rgba(255, 255, 255, 0.9) !important; + opacity: 1; +} + +.bubble-me .message-attachments .fw-semibold { + color: rgba(255, 255, 255, 0.95) !important; +} + +.bubble-me .message-attachments i { + color: rgba(255, 255, 255, 0.9) !important; +} + +.history-loader { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem 0; + color: var(--bs-secondary-color); +} + +.history-loader .spinner-border { + width: 1rem; + height: 1rem; +} + +/* Выпадающее меню вложений */ +.dropdown-menu { + min-width: 200px; + border-radius: $radius-md; + @include shadow($shadow-md); + padding: 8px; +} + +.dropdown-item { + border-radius: $radius-sm; + padding: 10px 12px; + @include transition(background-color, $transition-fast); +} + +.dropdown-item:hover { + background: var(--bs-secondary-bg); +} + +.dropdown-item i { + width: 24px; +} + +.composer-tools { + display: flex; + gap: var(--space-1); + align-items: center; +} + +/* ───────────────────────────────────────────────────────────── + TYPING INDICATOR +────────────────────────────────────────────────────────────── */ +.chat-typing-indicator { + padding: var(--space-2) var(--space-3); + display: flex; + align-items: center; + gap: var(--space-2); + font-size: 0.875rem; + color: var(--bs-secondary-color); + font-style: italic; + background: var(--bs-body-bg); + border-top: 1px solid var(--bs-border-color); + flex-shrink: 0; + + &.d-none { + display: none !important; + } +} + +.typing-dots { + display: inline-flex; + align-items: center; + gap: 4px; + + span { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--bs-secondary-color); + opacity: 0.6; + animation: typingBounce 1.4s infinite both; + + &:nth-child(2) { + animation-delay: 0.2s; + } + + &:nth-child(3) { + animation-delay: 0.4s; + } + } +} + +@keyframes typingBounce { + 0%, 60%, 100% { + transform: translateY(0); + } + 30% { + transform: translateY(-8px); + } +} + +.typing-text { + font-size: 0.875rem; + color: var(--bs-secondary-color); +} + +/* ───────────────────────────────────────────────────────────── + HISTORY LOADER +────────────────────────────────────────────────────────────── */ +.history-loader { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: var(--space-3) 0; + color: var(--bs-secondary-color); + font-size: 0.875rem; + + .spinner-border { + width: 1rem; + height: 1rem; + border-width: 2px; + } + + &.d-none { + display: none !important; + } +} + +.initial-loader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--space-5) 0; + color: var(--bs-secondary-color); + + .spinner-border { + width: 3rem; + height: 3rem; + } + + p { + margin-top: var(--space-3); + font-size: 1rem; + } +} + +/* ───────────────────────────────────────────────────────────── + SCROLL TO BOTTOM BUTTON +────────────────────────────────────────────────────────────── */ +.scroll-to-bottom { + position: fixed; + right: var(--space-3); + bottom: calc(var(--space-3) + 80px); + z-index: $z-dropdown; + display: none; + width: 44px; + height: 44px; + @include circle(); + padding: 0 !important; + border: none !important; + background: var(--bs-primary) !important; + color: #fff !important; + @include shadow($shadow-lg, rgba(var(--bs-primary-rgb), 0.25)); + @include transition((transform, box-shadow), $transition-fast); + display: inline-flex; + align-items: center; + justify-content: center; + opacity: 0; + pointer-events: none; + + i { + font-size: 1.25rem; + } + + &:hover { + transform: scale(1.08); + box-shadow: 0 6px 20px rgba(var(--bs-primary-rgb), 0.35) !important; + } + + &:active { + transform: scale(0.95); + } +} + +.scroll-to-bottom.show { + display: inline-flex; + opacity: 1; + pointer-events: auto; + animation: fadeInUp 0.3s ease; +} + +/* Badge непрочитанных на кнопке скролла */ +.scroll-to-bottom { + position: relative; // Для позиционирования badge + + .unread-badge { + position: absolute; + top: -6px; + right: -6px; + min-width: 20px; + height: 20px; + padding: 0 6px; + @include flex-center(); + @include radius($badge-radius); + background: var(--bs-danger); + color: #fff; + font-size: 11px; + font-weight: 600; + line-height: 1; + @include shadow($shadow-md); + transition: opacity $transition-fast, transform $transition-fast; + z-index: 1; + + // Скрытое состояние + &.badge-hidden { + opacity: 0; + transform: scale(0.5); + pointer-events: none; + } + } + + // Когда есть непрочитанные + &.has-unread { + .unread-badge { + opacity: 1; + transform: scale(1); + animation: badgePulse 2s ease-in-out infinite; + } + } +} + +@keyframes badgePulse { + 0%, 100% { + box-shadow: 0 2px 8px rgba(var(--bs-danger-rgb), 0.3); + } + 50% { + box-shadow: 0 2px 12px rgba(var(--bs-danger-rgb), 0.5); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +// Mobile стили для scroll button +@media (max-width: 767.98px) { + .scroll-to-bottom { + right: var(--space-2); + bottom: calc(var(--space-2) + 70px); // Учитываем composer на мобилке + width: 40px; + height: 40px; + + i { + font-size: 1.125rem; + } + + .unread-badge { + top: -4px; + right: -4px; + min-width: 18px; + height: 18px; + font-size: 10px; + } + } +} + +/* ───────────────────────────────────────────────────────────── + MESSAGES LAYOUT +────────────────────────────────────────────────────────────── */ +// Выравнивание аватара по низу сообщения +.d-flex.msg { + align-items: flex-end; +} + +/* ───────────────────────────────────────────────────────────── + BUBBLES & AVATARS +────────────────────────────────────────────────────────────── */ +.bubble { + border-radius: $radius-lg; + padding: var(--space-1) var(--space-2); + word-break: break-word; + white-space: pre-line; /* Сохраняет \n, но схлопывает пробелы из HTML */ + max-width: 70%; /* Ограничиваем ширину bubble */ + @include shadow($shadow-xs); + user-select: text; /* Разрешаем выделение текста */ + cursor: text; /* Курсор текстового выделения */ + + // Наследование white-space для вложенных элементов + > div { + white-space: inherit; + } + + // Специально для голосований - схлопываем whitespace + .poll-widget, + .poll-question, + .poll-options, + .poll-option, + .poll-footer { + white-space: normal; + } +} + +.bubble-me { + background: color-mix(in srgb, var(--bs-primary) 92%, #fff 0%); + color: #fff; +} + +.bubble-other { + background: var(--bs-secondary-bg); + color: var(--bs-body-color); +} + +.mini-ava { + width: 32px; + height: 32px; + flex-shrink: 0; /* Не сжимать аватар */ + border-radius: 50%; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background: var(--bs-tertiary-bg); + border: 1px solid var(--bs-border-color); + + img { + width: 100%; + height: 100%; + object-fit: cover; /* Кадрируем без деформации */ + display: block; + } + + i { + font-size: 20px; + color: var(--bs-secondary-color); + } +} + +/* ───────────────────────────────────────────────────────────── + DIVIDERS (день / непрочитанные) +────────────────────────────────────────────────────────────── */ +.day-divider, +.unread-divider { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-1); + margin: var(--space-1) 0 var(--space-2); + font-size: 0.85rem; + line-height: 1; + font-weight: 700; + pointer-events: none; + user-select: none; +} + +.day-divider::before, +.day-divider::after, +.unread-divider::before, +.unread-divider::after { + content: ""; + flex: 1 1 auto; + height: 1px; + background: color-mix(in srgb, var(--bs-border-color), transparent 30%); +} + +.day-divider > span, +.unread-divider > span { + flex: 0 0 auto; + padding: 0 var(--space-1); + background: transparent; +} + +.day-divider { + color: var(--bs-secondary-color); +} + +.unread-divider { + color: var(--bs-danger); +} diff --git a/backend/static/scss_backup_20260115/components/chat/_chat-enhanced.scss b/backend/static/scss_backup_20260115/components/chat/_chat-enhanced.scss new file mode 100644 index 00000000..cf37029d --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_chat-enhanced.scss @@ -0,0 +1,298 @@ +/** + * _chat-enhanced.scss + * Enhanced chat features: replies, forwards, reactions, attachments + */ + +/* ───────────────────────────────────────────────────────────── + REPLY PREVIEW +────────────────────────────────────────────────────────────── */ +.reply-preview { + font-size: 0.85rem; + padding: 4px 8px; + background: rgba(0, 0, 0, 0.05); + border-left: 3px solid var(--bs-primary); + border-radius: $radius-xs; + + i { + color: var(--bs-primary); + } +} + +/* ───────────────────────────────────────────────────────────── + FORWARD INFO +────────────────────────────────────────────────────────────── */ +.forward-info { + font-size: 0.85rem; + color: var(--bs-secondary); + font-style: italic; + + i { + color: var(--bs-info); + } +} + +/* ───────────────────────────────────────────────────────────── + MESSAGE ACTIONS (shown on hover) +────────────────────────────────────────────────────────────── */ +.msg { + position: relative; + + &:hover .message-actions { + opacity: 1; + } + + &:hover .quick-reactions { + opacity: 1; + pointer-events: all; + } + + &.new { + animation: slideIn 0.3s ease-out; + } + + &.system-message { + justify-content: center !important; + + .bubble { + background: #f8f9fa; + border: 1px dashed #dee2e6; + color: #6c757d; + text-align: center; + font-size: 0.9rem; + font-style: italic; + } + } + + .bubble { + em.text-muted { + font-size: 0.9rem; + opacity: 0.7; + } + + .bi-pencil-fill { + font-size: 0.7rem; + opacity: 0.6; + } + + .bi-pin-angle-fill { + color: var(--bs-warning); + font-size: 1rem; + } + } +} + +.message-actions { + position: absolute; + top: -12px; + right: 10px; + background: white; + border: 1px solid #dee2e6; + border-radius: $radius-pill; + padding: 2px 4px; + @include shadow($shadow-sm); + opacity: 0; + @include transition(opacity, $transition-fast); + z-index: 10; + + .btn { + padding: 2px 6px; + font-size: 0.85rem; + } +} + +/* ───────────────────────────────────────────────────────────── + REACTIONS +────────────────────────────────────────────────────────────── */ +.reactions { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.reaction-btn { + border: 1px solid #dee2e6; + background: white; + border-radius: $radius-md; + padding: 2px 8px; + font-size: 0.9rem; + cursor: pointer; + @include transition-all($transition-fast); + + &:hover { + background: #f8f9fa; + transform: scale(1.1); + } + + &.active { + background: #e3f2fd; + border-color: var(--bs-primary); + } + + .count { + font-size: 0.75rem; + color: #666; + margin-left: 2px; + } +} + +/* ───────────────────────────────────────────────────────────── + QUICK REACTIONS (on hover) +────────────────────────────────────────────────────────────── */ +.quick-reactions { + position: absolute; + bottom: -20px; + left: 50%; + transform: translateX(-50%); + background: white; + border: 1px solid #dee2e6; + border-radius: $radius-pill; + padding: 4px 8px; + @include shadow($shadow-md); + opacity: 0; + pointer-events: none; + @include transition(opacity, $transition-fast); + z-index: 20; + display: flex; + gap: 4px; + + button { + border: none; + background: transparent; + font-size: 1.1rem; + padding: 4px 8px; + cursor: pointer; + border-radius: $radius-sm; + @include transition(background, $transition-fast); + + &:hover { + background: #f0f0f0; + } + } +} + +/* ───────────────────────────────────────────────────────────── + ATTACHMENTS +────────────────────────────────────────────────────────────── */ +.attachments { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.attachment-image { + max-width: 200px; + max-height: 200px; + border-radius: $radius-sm; + cursor: pointer; + @include transition(transform, $transition-fast); + + &:hover { + transform: scale(1.05); + } +} + +.attachment-file { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: rgba(0, 0, 0, 0.05); + border-radius: $radius-sm; + text-decoration: none; + color: inherit; + font-size: 0.9rem; + + &:hover { + background: rgba(0, 0, 0, 0.1); + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT LIST ITEMS +────────────────────────────────────────────────────────────── */ +.chat-list-item { + position: relative; + + &.pinned { + background: #fffbeb; + border-left: 4px solid var(--bs-warning); + } +} + +/* ───────────────────────────────────────────────────────────── + BADGES & INDICATORS +────────────────────────────────────────────────────────────── */ +.mention-badge { + background: #ef4444; + color: white; + border-radius: 12px; + padding: 2px 8px; + font-size: 0.75rem; + font-weight: bold; +} + +.thread-indicator { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 0.85rem; + color: var(--bs-primary); + cursor: pointer; + + &:hover { + text-decoration: underline; + } +} + +/* ───────────────────────────────────────────────────────────── + INDICATORS +────────────────────────────────────────────────────────────── */ +#typing { + padding: 4px 12px; + font-style: italic; +} + +#editing-indicator, +#reply-indicator { + margin-bottom: 8px; + border-radius: 8px; +} + +/* ───────────────────────────────────────────────────────────── + ANIMATIONS +────────────────────────────────────────────────────────────── */ +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ───────────────────────────────────────────────────────────── + MOBILE RESPONSIVE +────────────────────────────────────────────────────────────── */ +@media (max-width: 768px) { + .message-actions { + position: static; + opacity: 1; + margin-top: 8px; + } + + .quick-reactions { + position: static; + transform: none; + opacity: 1; + pointer-events: all; + margin-top: 8px; + justify-content: center; + } + + .attachment-image { + max-width: 150px; + max-height: 150px; + } +} diff --git a/backend/static/scss_backup_20260115/components/chat/_chat-list-enhanced.scss b/backend/static/scss_backup_20260115/components/chat/_chat-list-enhanced.scss new file mode 100644 index 00000000..90a5271b --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_chat-list-enhanced.scss @@ -0,0 +1,320 @@ +/** + * _chat-list-enhanced.scss + * Enhanced chat list with context menus, animations, avatars + */ + +/* ───────────────────────────────────────────────────────────── + CONTEXT MENU +────────────────────────────────────────────────────────────── */ +.context-menu { + position: absolute; + background: white; + border: 1px solid #dee2e6; + border-radius: $radius-sm; + @include shadow($shadow-md); + padding: 4px 0; + min-width: 200px; + z-index: 9999; + + button { + width: 100%; + border: none; + background: transparent; + padding: 8px 16px; + text-align: left; + cursor: pointer; + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 8px; + + &:hover { + background: #f8f9fa; + } + } + + hr { + margin: 4px 0; + border-color: #dee2e6; + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT ROW +────────────────────────────────────────────────────────────── */ +.chat-row { + border-top: 1px solid var(--bs-border-color); + overflow: hidden; + min-height: 60px; + + &.updated { + background: #e3f2fd; + @include transition(background, $transition-medium); + } + + &.pinned { + background: #fffbeb; + border-left: 4px solid var(--bs-warning); + } + + .flex-grow-1 { + min-width: 0; + overflow: hidden; + + a, + .card-title { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; + } + } + + [data-last-preview], + .card-subtitle { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; + } + + &:hover .chat-actions-menu .btn { + opacity: 1; + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT SECTION +────────────────────────────────────────────────────────────── */ +.chat-section { + overflow: visible !important; + position: relative; + z-index: 1; + + &:has(.dropdown-menu.show) { + z-index: 1000; + } + + .card-header { + text-align: center; + border-bottom: 1px solid var(--bs-border-color); + + .card-icon { + display: none; + } + + .card-meta { + width: 100%; + text-align: center; + } + } + + .list-chats { + overflow: visible !important; + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT AVATARS - КРУГЛЫЕ! +────────────────────────────────────────────────────────────── */ +.chat-avatar, +.card-icon img, +.chat-list-wrapper .card-icon, +.chat-row .card-icon, +.chat-section .card-icon { + border-radius: 50% !important; +} + +.chat-avatar, +.card-icon img { + width: 44px !important; + height: 44px !important; + object-fit: cover; + border: 2px solid var(--bs-border-color); +} + +.chat-list-wrapper .card-icon, +.chat-row .card-icon { + width: 44px !important; + height: 44px !important; + min-width: 44px; + min-height: 44px; + border-radius: 50% !important; + overflow: hidden; +} + +.chat-avatar-group { + display: flex; + align-items: center; + + img { + margin-left: -8px; + border: 2px solid white; + border-radius: 50% !important; + + &:first-child { + margin-left: 0; + } + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT ACTIONS MENU +────────────────────────────────────────────────────────────── */ +.chat-actions-menu { + display: flex; + align-items: center; + flex-shrink: 0; + + .btn { + opacity: 0; + @include transition(opacity, $transition-fast); + + &:focus, + &[aria-expanded="true"] { + opacity: 1; + } + } + + .dropdown-menu { + font-size: 0.9rem; + min-width: 200px; + z-index: 9999; + position: fixed !important; + } + + .dropdown-item { + padding: 0.5rem 1rem; + + i { + width: 20px; + text-align: center; + } + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT TYPE SIDEBAR +────────────────────────────────────────────────────────────── */ +:root { + --chat-sidebar-offset: 120px; +} + +.chat-type-sidebar { + flex-shrink: 0; + align-self: flex-start; + position: sticky; + top: calc(var(--navbar-h, 60px) + var(--chat-sidebar-offset, 120px)); + + .card { + max-height: calc(100vh - var(--navbar-h, 60px) - var(--chat-sidebar-offset, 120px) - 24px); + overflow-y: auto; + } +} + +.chat-type-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.35rem; + position: relative; + cursor: pointer; + @include transition((background-color, opacity), $transition-fast); + + &[style*="display: none"] { + display: none !important; + } + + &:hover { + background-color: var(--bs-light) !important; + } + + &.active { + background-color: var(--bs-primary) !important; + color: white !important; + + i { + color: white; + } + + .badge { + background-color: rgba(255, 255, 255, 0.3) !important; + color: white !important; + border-color: white !important; + } + } + + .badge { + position: absolute; + top: 0.25rem; + right: 0.25rem; + min-width: 1.25rem; + height: 1.25rem; + padding: 0.15rem 0.3rem; + border-radius: 50%; + font-size: 0.65rem; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + } +} + +/* ───────────────────────────────────────────────────────────── + MOBILE VERSION +────────────────────────────────────────────────────────────── */ +@media (max-width: 768px) { + .chat-list-wrapper { + flex-direction: column !important; + } + + .chat-type-sidebar { + width: 100% !important; + order: -1; + position: static; + + .card { + max-height: none; + overflow: hidden; + padding: 0.25rem 0.5rem; + width: 100%; + } + + .list-group { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + gap: 0.5rem; + width: 100%; + overflow-x: auto; + overflow-y: hidden; + padding: 0 0.25rem 0.25rem; + scrollbar-width: thin; + scroll-snap-type: x proximity; + -webkit-overflow-scrolling: touch; + } + } + + .chat-type-btn { + flex: 0 0 auto; + min-width: 60px; + max-width: 80px; + padding: 0.5rem 0.25rem !important; + border-radius: 0.75rem !important; + border: 1px solid var(--bs-border-color) !important; + scroll-snap-align: start; + + i { + font-size: 1.25rem !important; + } + + .badge { + font-size: 0.6rem; + min-width: 1.1rem; + height: 1.1rem; + } + } +} diff --git a/backend/static/scss_backup_20260115/components/chat/_chat-new-messages-indicator.scss b/backend/static/scss_backup_20260115/components/chat/_chat-new-messages-indicator.scss new file mode 100644 index 00000000..f9f3c6f7 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_chat-new-messages-indicator.scss @@ -0,0 +1,149 @@ +/** + * _chat-new-messages-indicator.scss + * Индикатор новых сообщений в чате + * Кнопка для прокрутки к новым сообщениям + */ + +/* ───────────────────────────────────────────────────────────── + NEW MESSAGES INDICATOR +────────────────────────────────────────────────────────────── */ +.new-messages-indicator { + position: absolute; + bottom: 20px; + right: 20px; + z-index: 1000; + padding: 10px 16px; + min-width: 180px; + background: var(--bs-primary, #0d6efd); + color: white; + border: none; + border-radius: 24px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + font-size: 14px; + font-weight: 500; + opacity: 0; + transform: translateY(10px); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + pointer-events: none; + display: none; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + + &.show { + opacity: 1; + transform: translateY(0); + pointer-events: all; + animation: pulse 0.6s ease-in-out; + } + + &:hover { + background: var(--bs-primary-dark, #0a58ca); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); + transform: translateY(-2px); + } + + &:active { + transform: translateY(0) scale(0.98); + } + + svg { + width: 20px; + height: 20px; + flex-shrink: 0; + } + + .badge { + display: inline-block; + min-width: 20px; + padding: 2px 6px; + font-size: 12px; + font-weight: 600; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 10px; + background-color: rgba(255, 255, 255, 0.3); + color: white; + margin: 0 4px; + } + + /* Варианты расположения */ + &.left { + right: auto; + left: 20px; + } + + &.center { + left: 50%; + right: auto; + transform: translateX(-50%) translateY(10px); + + &.show { + transform: translateX(-50%) translateY(0); + } + + &:hover { + transform: translateX(-50%) translateY(-2px); + } + } +} + +/* ───────────────────────────────────────────────────────────── + CHAT SCROLL CONTAINER +────────────────────────────────────────────────────────────── */ +#chatScroll { + position: relative; +} + +/* ───────────────────────────────────────────────────────────── + ANIMATIONS +────────────────────────────────────────────────────────────── */ +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +/* ───────────────────────────────────────────────────────────── + RESPONSIVE +────────────────────────────────────────────────────────────── */ +@media (max-width: 768px) { + .new-messages-indicator { + bottom: 15px; + right: 15px; + min-width: 160px; + padding: 8px 14px; + font-size: 13px; + + svg { + width: 18px; + height: 18px; + } + + .badge { + font-size: 11px; + min-width: 18px; + padding: 2px 5px; + } + } +} + +/* ───────────────────────────────────────────────────────────── + DARK THEME +────────────────────────────────────────────────────────────── */ +@media (prefers-color-scheme: dark) { + .new-messages-indicator { + background: #1a73e8; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + + &:hover { + background: #1557b0; + } + } +} diff --git a/backend/static/scss_backup_20260115/components/chat/_chat-polls.scss b/backend/static/scss_backup_20260115/components/chat/_chat-polls.scss new file mode 100644 index 00000000..827d4cc8 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_chat-polls.scss @@ -0,0 +1,187 @@ +/** + * _chat-polls.scss + * Стили для голосований в чате + */ + +/* ───────────────────────────────────────────────────────────── + POLL MODAL +────────────────────────────────────────────────────────────── */ +#pollModal .modal-dialog { + display: flex; + align-items: center; + min-height: calc(100% - 1rem); + + @media (min-width: 576px) { + min-height: calc(100% - 3.5rem); + max-width: 500px; + margin: 1.75rem auto; + } +} + +#pollModal .remove-option { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +#pollOptions .input-group-text { + min-width: 40px; + justify-content: center; + font-weight: 600; +} + +/* ───────────────────────────────────────────────────────────── + POLL WIDGET +────────────────────────────────────────────────────────────── */ +.poll-widget { + padding: 1rem; + background: var(--bs-secondary-bg); + border-radius: $radius-sm; + margin: 0.5rem 0; +} + +.poll-question { + font-size: 1.1rem; + color: var(--bs-body-color); + font-weight: 400; +} + +.poll-option { + position: relative; + + &.voted { + background: rgba(var(--bs-success-rgb), 0.1); + padding: 0.5rem; + border-radius: $radius-xs; + + .progress-bar { + background-color: var(--bs-success); + } + } +} + +.btn-poll-option { + @include transition-all($transition-fast); + + &:hover { + background: var(--bs-primary); + color: white; + border-color: var(--bs-primary); + transform: translateX(4px); + } +} + +.poll-option-result { + .progress { + background-color: rgba(0, 0, 0, 0.1); + } + + .progress-bar { + background-color: var(--bs-primary); + @include transition(width, $transition-slow); + } +} + +.poll-footer { + border-top: 1px solid rgba(0, 0, 0, 0.1); + padding-top: 0.75rem; +} + +/* ───────────────────────────────────────────────────────────── + POLL IN MESSAGE BUBBLES +────────────────────────────────────────────────────────────── */ +.message-bubble .poll-widget { + max-width: 500px; +} + +/* Голосования в сообщениях других пользователей */ +.bubble-other .poll-widget { + background: var(--bs-secondary-bg); +} + +/* Голосования в своих сообщениях */ +.bubble-me { + .poll-widget { + background: rgba(255, 255, 255, 0.15); + } + + .poll-question { + color: #fff; + } + + .poll-option-result { + .progress { + background-color: rgba(255, 255, 255, 0.2); + } + + .progress-bar { + background-color: rgba(255, 255, 255, 0.6); + } + } + + .poll-footer { + border-top-color: rgba(255, 255, 255, 0.2); + } + + .poll-voters-list { + background: rgba(255, 255, 255, 0.1); + } +} + +/* ───────────────────────────────────────────────────────────── + POLL VOTERS +────────────────────────────────────────────────────────────── */ +.poll-voters { + margin-top: 0.5rem; +} + +.poll-voters-toggle { + cursor: pointer; + transition: color 0.2s; + + &:hover { + color: var(--bs-primary) !important; + text-decoration: underline !important; + } + + i { + transition: transform 0.2s; + font-size: 0.75rem; + } +} + +.poll-voters-list { + animation: slideDown 0.2s ease-out; + background: rgba(0, 0, 0, 0.03); + padding: 0.5rem 1rem; + border-radius: 0.25rem; + margin-top: 0.5rem; + + [data-bs-theme="dark"] & { + background: rgba(255, 255, 255, 0.05); + } +} + +.voter-item { + padding: 0.25rem 0; + display: flex; + align-items: center; + + i { + opacity: 0.6; + font-size: 0.875rem; + } +} + +/* ───────────────────────────────────────────────────────────── + ANIMATIONS +────────────────────────────────────────────────────────────── */ +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-5px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/backend/static/scss_backup_20260115/components/chat/_message-context-menu.scss b/backend/static/scss_backup_20260115/components/chat/_message-context-menu.scss new file mode 100644 index 00000000..fa67ab0d --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_message-context-menu.scss @@ -0,0 +1,343 @@ +/** + * _message-context-menu.scss + * Контекстное меню сообщений (long-press / right-click) + */ + +/* ───────────────────────────────────────────────────────────── + MESSAGE INTERACTIVE +────────────────────────────────────────────────────────────── */ +[data-message-id] { + cursor: pointer; + user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; + position: relative; +} + +/* ───────────────────────────────────────────────────────────── + CONTEXT MENU +────────────────────────────────────────────────────────────── */ +.message-context-menu { + position: fixed; + z-index: 9999; + background: #ffffff; // Явно белый для светлой темы + border-radius: var(--radius-lg); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); + min-width: 280px; + max-width: 320px; + opacity: 0; + transform: scale(0.9); + transition: opacity 0.2s ease, transform 0.2s ease; + overflow: hidden; + + &.show { + opacity: 1; + transform: scale(1); + } + + // Мобильное позиционирование больше не отличается - убрано + // (контекстное меню позиционируется одинаково на всех устройствах) + + // Backdrop для мобильных + &::before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.4); + z-index: -1; + opacity: 0; + transition: opacity 0.2s ease; + pointer-events: none; + } + + &.show::before { + opacity: 1; + } + + // На десктопе backdrop не нужен + @media (hover: hover) and (pointer: fine) { + &::before { + display: none; + } + } +} + +/* ───────────────────────────────────────────────────────────── + MESSAGE HIGHLIGHT +────────────────────────────────────────────────────────────── */ +.message-highlighted { + background-color: color-mix(in srgb, var(--bs-primary), transparent 92%) !important; + transition: background-color 0.2s ease; +} + +/* ───────────────────────────────────────────────────────────── + QUICK REACTIONS (TELEGRAM-STYLE) +────────────────────────────────────────────────────────────── */ +// Floating панель с реакциями (отдельно от меню) +.quick-reactions-panel { + position: fixed; + z-index: 10000; // Выше чем меню (9999) + background: #ffffff; // Явно белый для светлой темы + border-radius: 24px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + width: 280px; // Фиксированная ширина как у меню + opacity: 0; + transform: translateY(10px) scale(0.95); + transition: opacity 0.2s ease, transform 0.2s ease, height 0.3s ease; + pointer-events: none; // Не блокирует клики когда невидима + overflow: hidden; // Для плавной анимации высоты + + &.show { + opacity: 1; + transform: translateY(0) scale(1); + pointer-events: auto; + } +} + +.quick-reactions-wrapper { + position: relative; + display: flex; + align-items: center; + justify-content: center; + padding: 6px 8px; +} + +.quick-reactions-list { + display: flex; + align-items: center; + gap: 2px; + flex-wrap: wrap; // Переносим смайлики на новые строки + overflow-x: auto; + overflow-y: hidden; + max-width: 100%; + padding: 4px 0; + + // Скрываем scrollbar + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +} + +.quick-reaction-btn { + flex-shrink: 0; + width: 36px; + height: 36px; + border: none; + background: transparent; + border-radius: 50%; + font-size: 24px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s cubic-bezier(0.34, 1.56, 0.64, 1); + position: relative; + + &:hover { + transform: scale(1.25); + background: var(--bs-secondary-bg); + } + + &:active { + transform: scale(1.1); + } + + // Кнопка "показать больше реакций" + // TODO: Изменить серый фон на белый для светлой темы (должен совпадать с quick-reaction-btn) и кнопка раскрытия меню со смайликами при белой теме сливается с фоном (не меняется от цвета темы) + &.quick-reaction-btn-more { + opacity: 0.6; + font-size: 20px; + + i { + transition: transform 0.3s ease; + } + + &:hover { + opacity: 1; + transform: scale(1.25); + + i { + transform: translateY(3px); + } + } + + &.expanded i { + transform: rotate(180deg); + } + } + + // Анимация появления по очереди + opacity: 0; + animation: reactionItemAppear 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; + + @for $i from 1 through 10 { + &:nth-child(#{$i}) { + animation-delay: #{$i * 0.03}s; + } + } +} + +@keyframes reactionItemAppear { + from { + opacity: 0; + transform: scale(0.5) translateY(8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +/* ───────────────────────────────────────────────────────────── + MENU ACTIONS +────────────────────────────────────────────────────────────── */ +.menu-actions { + padding: var(--space-2) 0; +} + +.menu-action { + width: 100%; + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-3) var(--space-4); + border: none; + background: transparent; + cursor: pointer; + transition: background-color 0.15s ease; + font-size: 15px; + color: var(--bs-body-color); + text-align: left; + + &:hover { + background-color: var(--bs-tertiary-bg); + } + + &:active { + background-color: var(--bs-secondary-bg); + } + + i { + font-size: 18px; + width: 24px; + text-align: center; + color: var(--bs-secondary-color); + } + + span { + flex: 1; + font-weight: 500; + } +} + +.menu-action-danger { + color: var(--bs-danger); + + i { + color: var(--bs-danger); + } + + &:hover { + background-color: color-mix(in srgb, var(--bs-danger), transparent 95%); + } +} + +/* ───────────────────────────────────────────────────────────── + TOAST NOTIFICATION +────────────────────────────────────────────────────────────── */ +.context-menu-toast { + position: fixed; + bottom: 80px; + left: 50%; + transform: translateX(-50%) translateY(20px); + background: rgba(0, 0, 0, 0.85); + color: white; + padding: var(--space-3) calc(var(--space-3) * 2); + border-radius: var(--badge-radius); + font-size: 14px; + font-weight: 500; + z-index: 10000; + opacity: 0; + transition: opacity 0.3s ease, transform 0.3s ease; + pointer-events: none; + white-space: nowrap; + + &.show { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +/* ───────────────────────────────────────────────────────────── + DARK THEME +────────────────────────────────────────────────────────────── */ +[data-bs-theme="dark"] { + .message-context-menu { + background: #2b2d31; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + } + + .message-highlighted { + background-color: rgba(88, 101, 242, 0.15) !important; + } + + .quick-reactions-panel { + background: #2b2d31; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); + } + + .quick-reaction-btn { + &:hover { + background: rgba(255, 255, 255, 0.1); + } + } + + .menu-action { + color: #dbdee1; + + &:hover { + background-color: #35373c; + } + + &:active { + background-color: #3f4147; + } + + i { + color: #b5bac1; + } + } + + .menu-action-danger { + color: #f23f43; + + i { + color: #f23f43; + } + + &:hover { + background-color: rgba(242, 63, 67, 0.1); + } + } +} + +/* ───────────────────────────────────────────────────────────── + UTILITIES +────────────────────────────────────────────────────────────── */ +.message-context-menu-enabled .reaction-button.add-reaction { + display: none !important; +} + +@keyframes vibrate { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-2px); } + 75% { transform: translateX(2px); } +} + +.message-vibrating { + animation: vibrate 0.1s linear; +} diff --git a/backend/static/scss_backup_20260115/components/chat/_message-inputs.scss b/backend/static/scss_backup_20260115/components/chat/_message-inputs.scss new file mode 100644 index 00000000..1012e42c --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_message-inputs.scss @@ -0,0 +1,234 @@ +/** + * message-inputs.scss + * Унифицированные стили для полей ввода сообщений и комментариев + */ + +$message-bg-default: transparent; +$message-bg-focused: transparent; + +.message-field { + display: flex; + align-items: center; + gap: var(--space-1); + width: 100%; + padding: 0.35rem 0.4rem; + border: 1px solid var(--bs-border-color); + border-radius: 999px; + background: $message-bg-default; + transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease; + --message-action-size: 42px; + flex-wrap: nowrap; // Важно: запрещаем перенос элементов на новую строку +} + +.message-field:focus-within { + border-color: var(--bs-primary); + box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.15); + background: $message-bg-focused; +} + +.message-field--stacked { + align-items: stretch; + flex-direction: column; + border-radius: 24px; + padding: 0.75rem; + gap: var(--space-1); +} + +.message-field--compact { + padding: 0.25rem 0.3rem; + border-radius: 24px; + --message-action-size: 36px; +} + +.message-input, +.composer-textarea { + flex: 1 1 auto; + min-width: 0; // Важно: позволяет сжиматься ниже размера контента + border: none !important; + background: transparent !important; + resize: none; + padding: 0.5rem 0.75rem; + line-height: 1.4; + font-size: 1rem; + color: inherit; + scrollbar-width: none; + box-shadow: none !important; + outline: none !important; + // Высота управляется через textareaAutoExpand.js для rows="1" + // Для остальных textarea используется стандартное поведение +} + +.message-field--compact .message-input { + font-size: 0.95rem; + // min-height удален - управляется textareaAutoExpand.js +} + +.message-field--stacked .message-input { + min-height: 96px; + padding: 0.35rem 0; +} + +.message-input:focus { + outline: none; + box-shadow: none; +} + +.message-input::-webkit-scrollbar { + display: none; +} + +// Кнопки действий в формах сообщений +// Размеры и форма определяются здесь, стили (цвета, эффекты) - в custom-bootstrap.scss +.message-icon-btn, +.btn-attachment, +.btn-send, +.message-send, +.btn-emoji { + width: var(--message-action-size, 42px) !important; + min-width: var(--message-action-size, 42px) !important; + height: var(--message-action-size, 42px) !important; + border-radius: 50% !important; + display: inline-flex !important; + align-items: center; + justify-content: center; + padding: 0 !important; + aspect-ratio: 1 / 1; + flex: 0 0 auto; + line-height: 1; + font-size: 1.05rem; + --bs-btn-padding-x: 0 !important; + --bs-btn-padding-y: 0 !important; + --bs-btn-border-radius: 50% !important; + box-sizing: border-box; +} + +// Кнопка отправки - дополнительная тень +.message-send, +.btn-send { + box-shadow: 0 4px 12px rgba(var(--bs-primary-rgb), 0.2); +} + +.message-attach { + position: relative; +} + +.message-attach .dropdown-menu { + margin-top: 0.35rem; + border-radius: 16px; + padding: 0.5rem; +} + +.message-emoji .dropdown-menu { + min-width: 260px; + padding: 0; + border-radius: 16px; + overflow: hidden; +} + +.chat-emoji-picker { + width: min(340px, 90vw); + max-width: 360px; + height: 340px; + border: none; + outline: none; + + // Основные переменные для светлой и тёмной темы + --background-color: var(--bs-body-bg); + --text-color: var(--bs-body-color); + --category-font-color: var(--bs-secondary-color); + --hover-color: var(--bs-secondary-bg); + --button-active-background: var(--bs-primary-bg-subtle, var(--bs-primary)); + --button-hover-background: var(--bs-secondary-bg); + --focus-indicator: var(--bs-primary); + --border-color: color-mix(in srgb, var(--bs-border-color), transparent 25%); + --input-border-color: color-mix(in srgb, var(--bs-border-color), transparent 30%); + --input-placeholder-color: var(--bs-secondary-color); + --input-focus-border-color: var(--bs-primary); + --indicator-color: var(--bs-primary); + + // RGB переменные для emoji-picker-element (для тёмной темы) + --rgb-background: var(--bs-body-bg-rgb); + --rgb-color: var(--bs-body-color-rgb); + --rgb-accent: var(--bs-primary-rgb); + --rgb-input: var(--bs-body-bg-rgb); + + // Скроллбар для светлой темы по умолчанию + --scrollbar-color: rgba(0, 0, 0, 0.3); + --scrollbar-background: rgba(0, 0, 0, 0.05); + + // Скрываем кнопку выбора тона кожи + button.favorites { + display: none !important; + } + + // Кастомный скроллбар для внутренней панели + ::part(tabpanel) { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-color) var(--scrollbar-background); + } + + ::part(tabpanel)::-webkit-scrollbar { + width: 8px; + } + + ::part(tabpanel)::-webkit-scrollbar-track { + background: var(--scrollbar-background); + } + + ::part(tabpanel)::-webkit-scrollbar-thumb { + background: var(--scrollbar-color); + border-radius: 4px; + } + + ::part(tabpanel)::-webkit-scrollbar-thumb:hover { + background: rgba(var(--bs-body-color-rgb), 0.4); + } +} + +// Дополнительные стили для тёмной темы +[data-bs-theme="dark"] { + .chat-emoji-picker { + // Явно устанавливаем цвета для тёмной темы + --background-color: var(--bs-body-bg); + --text-color: var(--bs-body-color); + --secondary-text-color: var(--bs-secondary-color); + --category-emoji-size: 1.125rem; + + // Скроллбар для темной темы + --scrollbar-color: rgba(255, 255, 255, 0.3); + --scrollbar-background: rgba(255, 255, 255, 0.05); + } +} + +.message-field--solo { + display: block; + border-radius: 24px; + padding: 0.75rem 1rem; +} + +.message-field--solo .message-input { + width: 100%; + min-height: 120px; + padding: 0; +} + +@media (max-width: 576px) { + .message-field { + // Уменьшаем размеры кнопок на мобильных + --message-action-size: 38px; + padding: 0.3rem 0.35rem; + gap: 0.25rem; + } + + .message-field--compact { + --message-action-size: 32px; + padding: 0.2rem 0.25rem; + } + + .message-input, + .composer-textarea { + // Уменьшаем отступы для экономии места + padding: 0.4rem 0.5rem; + font-size: 0.95rem; + } +} diff --git a/backend/static/scss_backup_20260115/components/chat/_message-reactions.scss b/backend/static/scss_backup_20260115/components/chat/_message-reactions.scss new file mode 100644 index 00000000..8b175808 --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_message-reactions.scss @@ -0,0 +1,335 @@ +/** + * _message-reactions.scss + * Реакции на сообщения (эмодзи) + */ + +/* ───────────────────────────────────────────────────────────── + REACTIONS CONTAINER +────────────────────────────────────────────────────────────── */ +.message-reactions { + display: flex; + flex-wrap: wrap; + gap: 6px; // Увеличен gap для лучшего дыхания + margin-top: var(--space-2); + align-items: center; +} + +/* ───────────────────────────────────────────────────────────── + REACTION BUTTON +────────────────────────────────────────────────────────────── */ +.reaction-button { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + border: 1px solid var(--bs-border-color); + border-radius: 12px; // Скругленные края как таблетка + background: var(--bs-body-bg); // Контрастный фон (белый/черный в зависимости от темы) + cursor: pointer; + transition: all 0.2s ease; + font-size: 14px; + line-height: 1; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); // Легкая тень для отделения от фона + + &:hover { + background: var(--bs-secondary-bg); + border-color: color-mix(in srgb, var(--bs-border-color), var(--bs-body-color) 30%); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + // Активная реакция (пользователь уже отреагировал) + &.active { + background: #e7f3ff; // Непрозрачный светло-голубой вместо полупрозрачного + border-color: var(--bs-primary); + color: var(--bs-primary); + box-shadow: 0 2px 4px rgba(var(--bs-primary-rgb), 0.2); + + &:hover { + background: #d0e9ff; // Чуть темнее при hover + } + + .count { + color: var(--bs-primary); + font-weight: 600; // Жирнее для активных + } + } + + // Эмодзи внутри кнопки + .emoji { + font-size: 16px; + line-height: 1; + } + + // Счётчик реакций + .count { + font-size: 12px; + font-weight: 500; + color: var(--bs-body-color); + } +} + +/* ───────────────────────────────────────────────────────────── + ADD REACTION BUTTON +────────────────────────────────────────────────────────────── */ +.reaction-button.add-reaction { + opacity: 0; + transition: opacity 0.2s ease; + border-style: dashed; + + &:hover { + opacity: 1; + border-style: solid; + } +} + +// Показывать кнопку добавления при наведении на сообщение (только на десктопе без контекстного меню) +@media (hover: hover) and (pointer: fine) { + [data-message-id]:not(.message-context-menu-enabled):hover .reaction-button.add-reaction, + .message-reactions:hover .reaction-button.add-reaction { + opacity: 1; + } +} + +// Если есть хотя бы одна реакция, всегда показывать кнопку добавления +.message-reactions:has(.reaction-button:not(.add-reaction)) .add-reaction { + opacity: 1; +} + +// Полностью скрыть кнопку "+" если включено контекстное меню +.message-context-menu-enabled .reaction-button.add-reaction { + display: none; +} + +/* ───────────────────────────────────────────────────────────── + EMOJI PICKER +────────────────────────────────────────────────────────────── */ +.emoji-picker { + background: var(--bs-body-bg); + border: 1px solid var(--bs-border-color); + border-radius: var(--radius-md); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: var(--space-2); + min-width: 200px; +} + +.emoji-picker-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 4px; +} + +.emoji-picker-button { + width: 40px; + height: 40px; + border: 1px solid transparent; + border-radius: var(--radius-sm); + background: transparent; + cursor: pointer; + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s ease; + + &:hover { + background: var(--bs-tertiary-bg); + border-color: var(--bs-border-color); + transform: scale(1.1); + } + + &:active { + transform: scale(0.95); + } +} + +/* ───────────────────────────────────────────────────────────── + ANIMATIONS +────────────────────────────────────────────────────────────── */ +@keyframes reactionPop { + 0% { + transform: scale(0); + opacity: 0; + } + 50% { + transform: scale(1.2); + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +.reaction-button.new { + animation: reactionPop 0.3s ease; +} + +/* ───────────────────────────────────────────────────────────── + MOBILE RESPONSIVE +────────────────────────────────────────────────────────────── */ +@media (max-width: 576px) { + .reaction-button { + padding: 3px 6px; + font-size: 12px; + + .emoji { + font-size: 14px; + } + + .count { + font-size: 11px; + } + } + + .emoji-picker-grid { + grid-template-columns: repeat(6, 1fr); + } + + .emoji-picker-button { + width: 36px; + height: 36px; + font-size: 20px; + } +} + +/* ───────────────────────────────────────────────────────────── + DARK THEME +────────────────────────────────────────────────────────────── */ +[data-bs-theme="dark"] { + .reaction-button { + background: #2b3035; // Темнее чем bubble для контраста + border-color: #495057; + color: #f8f9fa; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + + &:hover { + background: #343a40; + border-color: #6c757d; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4); + } + + &.active { + background: #0d3c61; + border-color: #0d6efd; + color: #4dabf7; + box-shadow: 0 2px 4px rgba(13, 110, 253, 0.3); + + &:hover { + background: #0a4a7d; + } + + .count { + color: #4dabf7; + font-weight: 600; + } + } + } + + .emoji-picker { + background: #212529; + border-color: #495057; + } + + .emoji-picker-button:hover { + background: #343a40; + border-color: #495057; + } + + .reaction-users-popup { + background: #2b3035; + border-color: #495057; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + + .reaction-users-header { + border-bottom-color: #495057; + } + + .user-name { + color: #f8f9fa; + + &:hover { + background: #343a40; + } + } + } +} + +/* ───────────────────────────────────────────────────────────── + REACTION USERS POPUP +────────────────────────────────────────────────────────────── */ +.reaction-users-popup { + position: fixed; + z-index: 9999; + background: var(--bs-body-bg); + border: 1px solid var(--bs-border-color); + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + min-width: 180px; + max-width: 280px; + opacity: 0; + transform: translateY(-4px); + transition: opacity 0.2s ease, transform 0.2s ease; + pointer-events: none; + + &.show { + opacity: 1; + transform: translateY(0); + pointer-events: all; + } +} + +.reaction-users-header { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + border-bottom: 1px solid var(--bs-border-color); + font-weight: 600; + + .reaction-emoji { + font-size: 20px; + } + + .reaction-count { + color: var(--bs-secondary); + font-size: 14px; + } +} + +.reaction-users-list { + max-height: 200px; + overflow-y: auto; + padding: 4px; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bs-border-color); + border-radius: 3px; + + &:hover { + background: var(--bs-secondary); + } + } +} + +.user-name { + padding: 8px 12px; + color: var(--bs-body-color); + font-size: 14px; + border-radius: 6px; + transition: background-color 0.15s ease; + + &:hover { + background: var(--bs-secondary-bg); + } + + &:not(:last-child) { + margin-bottom: 2px; + } +} diff --git a/backend/static/scss_backup_20260115/components/chat/_message-selection.scss b/backend/static/scss_backup_20260115/components/chat/_message-selection.scss new file mode 100644 index 00000000..6fbe819e --- /dev/null +++ b/backend/static/scss_backup_20260115/components/chat/_message-selection.scss @@ -0,0 +1,292 @@ +/** + * _message-selection.scss + * Стили для режима выделения сообщений + */ + +/* ───────────────────────────────────────────────────────────── + SELECTION MODE +────────────────────────────────────────────────────────────── */ +.selection-mode .msg { + cursor: pointer; + transition: background-color 0.2s; + position: relative; + padding-left: 40px !important; + + &.justify-content-end { + padding-left: 0 !important; + padding-right: 40px !important; + } + + &:hover { + background-color: rgba(0, 123, 255, 0.05); + } +} + +/* ───────────────────────────────────────────────────────────── + CHECKBOX +────────────────────────────────────────────────────────────── */ +.msg-select-checkbox { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + width: 24px; + height: 24px; + border: 2px solid var(--bs-border-color); + border-radius: 50%; + background: var(--bs-body-bg); + display: none; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s; + z-index: 10; + flex-shrink: 0; + + .selection-mode & { + display: flex; + } + + &:hover { + border-color: var(--bs-primary); + background: color-mix(in srgb, var(--bs-primary), transparent 90%); + } + + &.selected { + background: var(--bs-primary); + border-color: var(--bs-primary); + + &::after { + content: '✓'; + color: white; + font-size: 14px; + font-weight: bold; + } + } +} + +.msg.justify-content-end .msg-select-checkbox { + left: auto; + right: 8px; +} + +.msg.selected { + background-color: color-mix(in srgb, var(--bs-primary), transparent 90%); +} + +/* ───────────────────────────────────────────────────────────── + ACTIONS BAR +────────────────────────────────────────────────────────────── */ +.selection-actions-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--bs-body-bg); + border-top: 2px solid var(--bs-primary); + padding: 12px 20px; + display: none; + align-items: center; + justify-content: space-between; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); + z-index: $z-modal; + animation: slideUp 0.3s ease-out; + + &.active { + display: flex; + } +} + +@keyframes slideUp { + from { + transform: translateY(100%); + } + to { + transform: translateY(0); + } +} + +.selection-count { + font-weight: 600; + color: var(--bs-primary); + font-size: 16px; +} + +.selection-actions { + display: flex; + gap: 10px; + + .btn { + padding: 8px 20px; + font-weight: 500; + border-radius: var(--button-radius); + transition: all 0.2s; + + &:hover { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + } + } +} + +/* ───────────────────────────────────────────────────────────── + FORWARD MODAL +────────────────────────────────────────────────────────────── */ +.chat-forward-modal { + .modal-dialog { + max-width: 500px; + } +} + +.chat-list-item { + padding: 12px 16px; + border-bottom: 1px solid var(--bs-border-color); + cursor: pointer; + transition: background-color 0.2s; + display: flex; + align-items: center; + gap: 12px; + + &:hover { + background-color: var(--bs-tertiary-bg); + } + + &:last-child { + border-bottom: none; + } +} + +.chat-list-avatar { + width: 48px; + height: 48px; + border-radius: 50%; + background: var(--bs-tertiary-bg); + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + color: var(--bs-secondary-color); + flex-shrink: 0; + overflow: hidden; + position: relative; + + img { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + top: 0; + left: 0; + } +} + +.chat-list-info { + flex: 1; + min-width: 0; +} + +.chat-list-name { + font-weight: 600; + color: var(--bs-body-color); + margin-bottom: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.chat-list-subtitle { + font-size: 13px; + color: var(--bs-secondary-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.chat-list-loading, +.chat-list-empty { + padding: 40px 20px; + text-align: center; + color: var(--bs-secondary-color); +} + +/* ───────────────────────────────────────────────────────────── + SEARCH +────────────────────────────────────────────────────────────── */ +.chat-forward-search { + padding: 12px 16px; + border-bottom: 1px solid var(--bs-border-color); + + input { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--bs-border-color); + border-radius: var(--radius-md); + font-size: 14px; + background: var(--bs-body-bg); + color: var(--bs-body-color); + + &:focus { + outline: none; + border-color: var(--bs-primary); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--bs-primary), transparent 90%); + } + + &::placeholder { + color: var(--bs-secondary-color); + } + } +} + +/* ───────────────────────────────────────────────────────────── + FORWARDED INDICATOR +────────────────────────────────────────────────────────────── */ +.forwarded-indicator { + font-size: 12px; + color: var(--bs-secondary-color); + margin-bottom: 4px; + display: flex; + align-items: center; + gap: 4px; + font-style: italic; + + i { + font-size: 14px; + } +} + +/* ───────────────────────────────────────────────────────────── + MOBILE RESPONSIVE +────────────────────────────────────────────────────────────── */ +@media (max-width: 768px) { + .selection-actions-bar { + padding: 10px 15px; + } + + .selection-count { + font-size: 14px; + } + + .selection-actions .btn { + padding: 6px 16px; + font-size: 14px; + } + + .msg-select-checkbox { + width: 20px; + height: 20px; + left: 5px; + } + + .msg.justify-content-end .msg-select-checkbox { + right: 5px; + } + + .selection-mode .msg { + padding-left: 32px !important; + + &.justify-content-end { + padding-left: 0 !important; + padding-right: 32px !important; + } + } +} diff --git a/backend/static/scss_backup_20260115/custom-bootstrap.scss b/backend/static/scss_backup_20260115/custom-bootstrap.scss new file mode 100644 index 00000000..5bca1e5e --- /dev/null +++ b/backend/static/scss_backup_20260115/custom-bootstrap.scss @@ -0,0 +1,201 @@ +// ============================================================================= +// Custom Bootstrap Configuration +// ============================================================================= +// Этот файл переопределяет переменные Bootstrap ДО его компиляции +// Результат: полностью кастомизированный Bootstrap с нашей системой отступов + +// ============================================================================= +// 1. ИМПОРТ НАШИХ ПЕРЕМЕННЫХ +// ============================================================================= +// Сначала импортируем наши CSS переменные (для справки) +// Реальные значения ниже в SCSS переменных + +// ============================================================================= +// 2. ПЕРЕОПРЕДЕЛЕНИЕ BOOTSTRAP ПЕРЕМЕННЫХ +// ============================================================================= + +// ────────────────────────────────────────────────────────────── +// 2.1 Система отступов (8px grid) +// ────────────────────────────────────────────────────────────── +// Bootstrap использует $spacer для всех utility классов (.p-*, .m-* и т.д.) +// Переопределяем его на нашу 8px сетку + +$spacer: 1rem !default; // 16px - базовое значение + +// Переопределение всей шкалы spacing +$spacers: ( + 0: 0, // 0px + 1: 0.25rem, // 4px - var(--space-1) + 2: 0.5rem, // 8px - var(--space-2) + 3: 0.75rem, // 12px - var(--space-3) + 4: 1rem, // 16px - var(--space-4) + 5: 1.5rem, // 24px - var(--space-5) +) !default; + +// ────────────────────────────────────────────────────────────── +// 2.2 Grid система +// ────────────────────────────────────────────────────────────── +$grid-gutter-width: 0.5rem !default; // 8px - var(--space-2) + +// ────────────────────────────────────────────────────────────── +// 2.3 Компоненты - Padding +// ────────────────────────────────────────────────────────────── +// Переопределяем padding для всех Bootstrap компонентов + +// Cards +$card-spacer-y: 1rem !default; // var(--space-2) +$card-spacer-x: 1rem !default; // var(--space-2) + +// Alerts +$alert-padding-y: 1rem !default; // var(--space-2) +$alert-padding-x: 1rem !default; // var(--space-2) + +// Buttons +$btn-padding-y: 0.5rem !default; // var(--space-1) +$btn-padding-x: 1rem !default; // var(--space-2) + +$btn-padding-y-sm: 0.5rem !default; // var(--space-1) +$btn-padding-x-sm: 0.5rem !default; // var(--space-1) + +$btn-padding-y-lg: 1rem !default; // var(--space-2) +$btn-padding-x-lg: 1.5rem !default; // var(--space-3) + +// Form controls +$input-padding-y: 0.5rem !default; // var(--space-1) +$input-padding-x: 1rem !default; // var(--space-2) + +$input-padding-y-sm: 0.5rem !default; // var(--space-1) +$input-padding-x-sm: 0.5rem !default; // var(--space-1) + +$input-padding-y-lg: 1rem !default; // var(--space-2) +$input-padding-x-lg: 1.5rem !default; // var(--space-3) + +// Modals +$modal-inner-padding: 1rem !default; // var(--space-2) +$modal-header-padding-y: 1rem !default; // var(--space-2) +$modal-header-padding-x: 1rem !default; // var(--space-2) + +// Dropdowns +$dropdown-padding-x: 0 !default; +$dropdown-padding-y: 8px !default; // var(--space-1) +$dropdown-item-padding-y: 8px !default; // var(--space-1) +$dropdown-item-padding-x: 16px !default; // var(--space-2) + +// Navs +$nav-link-padding-y: 8px !default; // var(--space-1) +$nav-link-padding-x: 16px !default; // var(--space-2) + +// Navbar +$navbar-padding-y: 8px !default; // var(--space-1) +$navbar-padding-x: 16px !default; // var(--space-2) + +// Pagination +$pagination-padding-y: 8px !default; // var(--space-1) +$pagination-padding-x: 16px !default; // var(--space-2) + +$pagination-padding-y-sm: 8px !default; // var(--space-1) +$pagination-padding-x-sm: 8px !default; // var(--space-1) + +$pagination-padding-y-lg: 16px !default; // var(--space-2) +$pagination-padding-x-lg: 24px !default; // var(--space-3) + +// List groups +$list-group-item-padding-y: 1rem !default; // var(--space-2) +$list-group-item-padding-x: 1rem !default; // var(--space-2) + +// Breadcrumbs +$breadcrumb-padding-y: 0.5rem !default; // var(--space-1) +$breadcrumb-padding-x: 1rem !default; // var(--space-2) + +// Toasts +$toast-padding-x: 1rem !default; // var(--space-2) +$toast-padding-y: 0.5rem !default; // var(--space-1) + +// Offcanvas +$offcanvas-padding-y: 1rem !default; // var(--space-2) +$offcanvas-padding-x: 1rem !default; // var(--space-2) + +// Badges +$badge-padding-y: 0.5rem !default; // var(--space-1) +$badge-padding-x: 0.5rem !default; // var(--space-1) + +// ────────────────────────────────────────────────────────────── +// 2.4 Cards - переопределяем стили карточек (GLASSMORPHISM) +// ────────────────────────────────────────────────────────────── +$card-border-radius: 18px !default; +$card-border-width: 1px !default; +$card-border-color: rgba(255, 255, 255, 0.1) !default; // Светлая граница для glassmorphism +$card-bg: rgba(255, 255, 255, 0.05) !default; // Полупрозрачный для эффекта стекла +$card-box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37) !default; // Глубокая тень + +// Card header/footer +$card-cap-padding-y: 1rem !default; +$card-cap-padding-x: 1rem !default; +$card-cap-bg: transparent !default; // Прозрачный header/footer +$card-cap-color: null !default; + +// ────────────────────────────────────────────────────────────── +// 2.5 Дополнительные настройки (опционально) +// ────────────────────────────────────────────────────────────── + +// Border radius (если хотите унифицировать) +// $border-radius: 10px !default; // var(--radius-md) +// $border-radius-sm: 6px !default; // var(--radius-sm) +// $border-radius-lg: 14px !default; // var(--radius-lg) + +// ============================================================================= +// 3. ИМПОРТ ABSTRACTS (Design Tokens & Mixins) +// ============================================================================= +// Импортируем переменные и миксины ДО Bootstrap +@import "abstracts/variables"; +@import "abstracts/mixins"; + +// ============================================================================= +// 4. ИМПОРТ BOOTSTRAP +// ============================================================================= +// Импортируем Bootstrap ПОСЛЕ переопределения переменных +@import "../node_modules/bootstrap/scss/bootstrap"; + +// ============================================================================= +// 4.5 CSS ПЕРЕМЕННЫЕ (RUNTIME) +// ============================================================================= +// CSS custom properties для использования в браузере +@import "abstracts/css-variables"; + +// ============================================================================= +// 5. КАСТОМНЫЕ КОМПОНЕНТЫ +// ============================================================================= + +// Layout +@import "layout/chat-layout"; + +// Базовые компоненты +@import "components/common"; +@import "components/navbar"; +@import "components/sidebar"; +@import "components/page-header"; +@import "components/cards"; + +// Badge компоненты +@import "components/badges-bem"; + +// Чат компоненты (в подпапке chat/) +@import "components/chat/message-inputs"; +@import "components/chat/chat-detail"; +@import "components/chat/chat-date-groups"; +@import "components/chat/chat-list-enhanced"; +@import "components/chat/chat-enhanced"; +@import "components/chat/chat-polls"; +@import "components/chat/chat-new-messages-indicator"; +@import "components/chat/message-selection"; +@import "components/chat/message-context-menu"; +@import "components/chat/message-reactions"; + +// Дополнительные компоненты +@import "components/comments"; +@import "components/modals"; + +// Остальные компоненты (загружаются напрямую из CSS - не мигрировано) +// TODO: Мигрировать buttons, card-list, document-list, request-list, +// ios-search, feed-specific, base-app, request-detail + diff --git a/backend/static/scss_backup_20260115/layout/_base-app.scss b/backend/static/scss_backup_20260115/layout/_base-app.scss new file mode 100644 index 00000000..e68c0451 --- /dev/null +++ b/backend/static/scss_backup_20260115/layout/_base-app.scss @@ -0,0 +1,397 @@ +// ============================================================================= +// Base App Layout & Styles (SCSS) +// ============================================================================= +// Базовые стили приложения: layout, navbar, sidebar, формы, feed-элементы +// +// Зависимости: +// - Bootstrap custom build +// - CSS переменные из abstracts/_css-variables.scss + +// ============================================================================= +// ПЕРЕМЕННЫЕ +// ============================================================================= + +// Цвета +$primary-light: #007AFF; +$primary-dark: #0A84FF; +$link-hover-light: #0062CC; +$link-hover-dark: #59AFFF; + +// Фоны темной темы +$dark-bg: #1C1C1E; +$dark-color: #F2F2F7; +$dark-border: #3A3A3C; +$dark-tertiary-bg: #2C2C2E; + +// Типографика +$font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; +$font-size-base: 17px; + +// Размеры компонентов +$btn-border-radius: 10px; +$btn-min-height: 44px; +$form-control-border-radius: 10px; +$form-control-height: 44px; +$dropdown-border-radius: 13px; + +// Переходы +$btn-transition: background-color 0.3s, transform 0.1s; +$form-transition: box-shadow 0.2s ease, border-color 0.2s ease; +$ghost-btn-transition: background 0.2s ease; + +// Тени +$dropdown-shadow: 0 6px 24px rgba(0, 0, 0, 0.12); + +// Navbar +$navbar-border-width: 0.5px; + +// ============================================================================= +// ЦВЕТОВАЯ СХЕМА +// ============================================================================= + +:root { + --bs-primary: #{$primary-light}; + --bs-link-color: var(--bs-primary); + --bs-link-hover-color: #{$link-hover-light}; +} + +[data-bs-theme="dark"] { + --bs-body-bg: #{$dark-bg}; + --bs-body-color: #{$dark-color}; + --bs-border-color: #{$dark-border}; + --bs-tertiary-bg: #{$dark-tertiary-bg}; + --bs-primary: #{$primary-dark}; + --bs-link-color: var(--bs-primary); + --bs-link-hover-color: #{$link-hover-dark}; +} + +// ============================================================================= +// BODY & БАЗОВЫЕ СТИЛИ +// ============================================================================= + +body { + font-family: $font-family-base; + font-size: $font-size-base; + padding-top: var(--navbar-h); + overflow-x: hidden; + background-color: var(--bs-body-bg); + + // Safe area для iOS + @supports(padding-top: env(safe-area-inset-top)) { + padding-top: calc(var(--navbar-h) + env(safe-area-inset-top)); + } +} + +// ============================================================================= +// ФОРМЫ И КНОПКИ (БАЗОВЫЕ ПЕРЕОПРЕДЕЛЕНИЯ) +// ============================================================================= + +.btn { + border-radius: $btn-border-radius; + min-height: $btn-min-height; + transition: $btn-transition; + + &:hover { + transform: scale(1.02); + } +} + +.form-control { + border-radius: $form-control-border-radius; + height: $form-control-height; + font-size: $font-size-base; + + &:focus { + box-shadow: 0 0 0 2px rgba(var(--bs-primary-rgb), 0.25); + border-color: var(--bs-primary); + } +} + +.dropdown-menu { + border-radius: $dropdown-border-radius; + box-shadow: $dropdown-shadow; +} + +// ============================================================================= +// NAVBAR +// ============================================================================= + +.app-navbar { + position: fixed; + inset: 0 0 auto 0; + z-index: 1030; + border-bottom: $navbar-border-width solid var(--bs-border-color); +} + +// ============================================================================= +// LAYOUT GRID SYSTEM +// ============================================================================= + +.app-layout { + display: block; +} + +// Адаптивный контейнер +.content-container { + width: 100%; + margin-left: auto; + margin-right: auto; +} + +// Мобильные устройства +@media (max-width: 991.98px) { + .content-container { + padding-left: var(--bs-gutter-x, 0.75rem); + padding-right: var(--bs-gutter-x, 0.75rem); + } +} + +// Десктоп с grid layout +@media (min-width: 992px) { + // При наличии сайдбаров убираем padding и max-width + .app-layout.with-sidebar, + .app-layout.with-rightbar { + .content-container { + max-width: none; + padding-left: 0; + padding-right: 0; + } + } + + // Grid: sidebar + content + rightbar + .app-layout.with-sidebar.with-rightbar { + display: grid; + grid-template-columns: var(--sidebar-w) 1fr var(--rightbar-w); + gap: var(--layout-gap); + align-items: start; + } + + // Grid: sidebar + content + .app-layout.with-sidebar:not(.with-rightbar) { + display: grid; + grid-template-columns: var(--sidebar-w) 1fr; + gap: var(--layout-gap); + align-items: start; + } + + // Без сайдбаров - стандартный container + .app-layout:not(.with-sidebar):not(.with-rightbar) { + .content-container { + padding-left: var(--bs-gutter-x, 0.75rem); + padding-right: var(--bs-gutter-x, 0.75rem); + max-width: 960px; + } + } + + // Компенсация для .row внутри grid-layout + .app-layout.with-sidebar, + .app-layout.with-rightbar { + .content-container > .row { + margin-left: 0; + margin-right: 0; + + > [class*="col-"] { + padding-left: calc(var(--bs-gutter-x) * 0.5); + padding-right: calc(var(--bs-gutter-x) * 0.5); + } + } + + // Карточки внутри колонок + .container > .row > [class*="col-"] { + > .card, + > section.card { + max-width: 100%; + min-width: 0; + overflow: hidden; + } + } + } +} + +// Breakpoints для max-width +@media (min-width: 1200px) { + .app-layout:not(.with-sidebar):not(.with-rightbar) .content-container { + max-width: 1140px; + } +} + +@media (min-width: 1400px) { + .app-layout:not(.with-sidebar):not(.with-rightbar) .content-container { + max-width: 1320px; + } +} + +// ============================================================================= +// SIDEBAR +// ============================================================================= + +#sidebar { + --bs-offcanvas-width: var(--sidebar-w); + border-radius: 0 13px 13px 0; + + @media (min-width: 992px) { + &.offcanvas-lg { + position: sticky; + top: var(--navbar-h); + height: calc(100dvh - var(--navbar-h)); + align-self: start; + overflow: auto; + border-right: $navbar-border-width solid var(--bs-border-color); + background: var(--bs-body-bg); + transform: none !important; + visibility: visible !important; + } + } +} + +// ============================================================================= +// NOTIFICATION BELL +// ============================================================================= + +.bell-wrapper { + position: relative; + line-height: 1; + display: inline-block; +} + +.bell-dot { + position: absolute; + top: -2px; + right: -2px; + width: 9px; + height: 9px; + border-radius: 999px; + background: var(--bs-danger); + color: #fff; + font-size: 7px; + line-height: 9px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.65); +} + +// ============================================================================= +// BUTTONS & GHOST BUTTONS +// ============================================================================= + +.btn-ghost { + background: color-mix(in srgb, var(--bs-body-bg) 94%, transparent); + color: var(--bs-body-color); + padding: var(--space-1); + border-radius: 999px; + font-size: 0.9rem; + transition: $ghost-btn-transition; + + &:hover { + background: color-mix(in srgb, var(--bs-body-bg) 88%, transparent); + } + + .bi { + font-size: 1rem; + margin-right: 0.35rem; + vertical-align: -2px; + } + + // Текст кнопок с toggle (Скрыть/Показать) + .txt-close { + display: none; + } + + &[aria-expanded="true"] { + .txt-open { + display: none; + } + + .txt-close { + display: inline; + } + } +} + +// ============================================================================= +// FEED ELEMENTS +// ============================================================================= + +// Блоки типа "Новые сотрудники" +.feed-new { + border-radius: var(--feed-radius); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%); + background: var(--bs-body-bg); + box-shadow: var(--feed-shadow); + padding: var(--card-padding); +} + +// Z-index для stretched-link +.card-body .stretched-link::after, +.feed-img .stretched-link::after { + z-index: 1; +} + +.collapse { + position: relative; + z-index: 2; +} + +// ============================================================================= +// ALPHA HEADERS (буквенные разделители) +// ============================================================================= + +.alpha-header { + position: sticky; + top: calc(var(--navbar-h) + var(--space-1)); + z-index: 1; + background: color-mix(in srgb, var(--bs-body-bg) 92%, transparent); + backdrop-filter: blur(6px); + border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%); + color: var(--bs-secondary-color); + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1); + border-radius: 999px; + font-weight: 700; + font-size: 0.85rem; + margin: var(--space-1) 0 var(--space-1) var(--space-1); +} + +.alpha-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: var(--bs-primary); + opacity: 0.6; +} + +// ============================================================================= +// ACTION BUTTONS +// ============================================================================= + +.action-btn { + min-height: unset; + padding: var(--space-1); + + .bi { + font-size: 1rem; + } +} + +// ============================================================================= +// TEXT UTILITIES +// ============================================================================= + +// Переносим длинные слова/ссылки +.feed-text, +.feed-full { + overflow-wrap: anywhere; + width: 100%; + margin-right: 0; + padding-right: 0; +} + +// Z-index для кнопки "Подробнее" +.readmore-toggle { + position: relative; + z-index: 2; +} diff --git a/backend/static/scss_backup_20260115/layout/_chat-layout.scss b/backend/static/scss_backup_20260115/layout/_chat-layout.scss new file mode 100644 index 00000000..3f9672ca --- /dev/null +++ b/backend/static/scss_backup_20260115/layout/_chat-layout.scss @@ -0,0 +1,61 @@ +// ============================================================================= +// Chat Layout +// ============================================================================= +// Глобальная структура страниц чата (не специфичные компоненты) + +// ============================================================================= +// 1. MAIN CONTAINER +// ============================================================================= + +main.main { + padding-bottom: 0 !important; +} + +// Специальное правило для страниц чата в grid layout +// Ограничиваем высоту main, чтобы не ломать grid и rightbar +@media (min-width: 992px) { + .app-layout.with-rightbar main.main:has(.chat-page), + .app-layout.with-sidebar main.main:has(.chat-page) { + max-height: calc(100vh - var(--navbar-h, 60px) - 2 * var(--layout-padding-y, 12px)); + overflow: hidden; + } +} + +// ============================================================================= +// 2. CHAT PAGE STRUCTURE +// ============================================================================= + +// Root контейнер страницы чата +@supports (height: 100svh) { + .chat-root { + height: calc(100svh - var(--navbar-h)); + } +} + +.chat-root > .row { + height: 100%; + min-height: 0; +} + +// Column layout +.chat-col { + display: flex; + flex-direction: column; + height: 93%; + min-height: 0; +} + +// ============================================================================= +// 3. CHAT BOX CONTAINER +// ============================================================================= + +.card.chat-box { + position: relative; + flex: 1 1 auto; + min-height: 0; + display: grid; + grid-template-rows: 1fr auto; /* messages + composer */ + overflow: hidden; + margin-bottom: 0; + background: var(--bs-body-bg); +} diff --git a/backend/static/scss_backup_20260115/main.scss b/backend/static/scss_backup_20260115/main.scss new file mode 100644 index 00000000..a6c7c5c2 --- /dev/null +++ b/backend/static/scss_backup_20260115/main.scss @@ -0,0 +1,85 @@ +// ============================================================================= +// Main Application SCSS +// ============================================================================= +// Главный файл для сборки всех стилей приложения (кроме Bootstrap) +// Компилируется в css/app.css + +// ============================================================================= +// 1. ABSTRACTS - Переменные, миксины, функции +// ============================================================================= +@import 'abstracts/index'; + +// ============================================================================= +// 2. LAYOUT - Структура приложения +// ============================================================================= +@import 'layout/base-app'; +@import 'layout/chat-layout'; + +// ============================================================================= +// 3. COMPONENTS - Переиспользуемые компоненты +// ============================================================================= + +// Базовые компоненты +@import 'components/buttons'; +@import 'components/cards'; +@import 'components/modals'; +@import 'components/navbar'; +@import 'components/sidebar'; +@import 'components/page-header'; +@import 'components/common'; + +// Spacing и layout utilities +@import 'components/layout-spacing'; +@import 'components/spacing-utils'; + +// iOS-стиль компоненты +@import 'components/ios-search'; +@import 'components/ios-components'; + +// Бейджи +@import 'components/badges-bem'; +@import 'components/badges-telegram-style'; + +// Комментарии +@import 'components/comments'; + +// Списки и уведомления +@import 'components/card-list'; +@import 'components/notifications'; + +// Заявления и документы +@import 'components/request-list'; +@import 'components/request-detail'; +@import 'components/document-list'; +@import 'components/recipientPicker'; +@import 'components/list-view'; + +// Feed компоненты +@import 'components/feed-specific'; +@import 'components/join-scroller'; +@import 'components/post-form'; + +// Модальные окна +@import 'components/modal-overrides'; +@import 'components/logout-modal'; + +// Employees компоненты +@import 'components/department-controls'; +@import 'components/team-wheel'; + +// Календарь и rightbar +@import 'components/rightbar-calendar'; + +// ============================================================================= +// 4. UTILITIES - Вспомогательные классы +// ============================================================================= +// @import 'utilities/spacing-utils'; +// @import 'utilities/index'; + +// ============================================================================= +// ПРИМЕЧАНИЯ +// ============================================================================= +// - Bootstrap подключается отдельно через bootstrap-custom.css +// - CSS переменные (--variable) определены в abstracts/_css-variables.scss +// - SCSS переменные ($variable) определены в abstracts/_variables.scss +// - Порядок импортов критичен для CSS каскада diff --git a/docs/reports/SCSS_ARCHITECTURE_AUDIT.md b/docs/reports/SCSS_ARCHITECTURE_AUDIT.md new file mode 100644 index 00000000..edcf2273 --- /dev/null +++ b/docs/reports/SCSS_ARCHITECTURE_AUDIT.md @@ -0,0 +1,461 @@ +# Аудит архитектуры backend/static/scss + +**Дата:** 15 января 2026 +**Аудитор:** GitHub Copilot +**Статус:** ⚠️ Требуется улучшение + +## 📊 Общая оценка: 7/10 + +### ✅ Сильные стороны (7 баллов) + +1. **Хорошая структура папок** (9/10) + - ✅ Логичное разделение: abstracts, layout, components, utilities + - ✅ Подпапка chat для chat компонентов + - ✅ Понятная организация + +2. **Именование файлов** (8/10) + - ✅ Префикс `_` для partials + - ✅ Kebab-case naming + - ✅ Понятные имена + +3. **Документация** (7/10) + - ✅ Есть BUTTONS_README.md + - ✅ Комментарии в main.scss + - ⚠️ Нет общего README в scss/ + +4. **Модульность** (9/10) + - ✅ 30 компонентов в components/ + - ✅ 10 chat модулей в components/chat/ + - ✅ Разделение abstracts + +## ⚠️ Проблемы и недостатки (3 балла снижения) + +### 🔴 Критические проблемы + +#### 1. Дублирование импортов компонентов (CRITICAL) + +**Проблема:** Многие компоненты импортируются дважды: +- Один раз в `main.scss` → компилируется в `app.css` +- Второй раз в `custom-bootstrap.scss` → компилируется в `bootstrap-custom.css` + +**Дублированные импорты:** +```scss +// В main.scss: +@import 'components/common'; +@import 'components/navbar'; +@import 'components/sidebar'; +@import 'components/page-header'; +@import 'components/cards'; +@import 'components/modals'; +@import 'components/comments'; + +// И ЕЩЕ РАЗ в custom-bootstrap.scss: +@import "components/common"; +@import "components/navbar"; +@import "components/sidebar"; +@import "components/page-header"; +@import "components/cards"; +@import "components/modals"; +@import "components/comments"; +``` + +**Последствия:** +- ❌ Эти стили дублируются в `app.css` И `bootstrap-custom.css` +- ❌ Увеличенный размер файлов +- ❌ Повторяющиеся селекторы +- ❌ Потенциальные конфликты специфичности + +**Затронутые компоненты:** +1. common +2. navbar +3. sidebar +4. page-header +4. cards +5. badges-bem +6. modals +7. comments + +**Размер дублирования:** ~1,500-2,000 строк SCSS дублируются! + +#### 2. Chat компоненты ТОЛЬКО в bootstrap-custom.css + +**Проблема:** Все 10 chat компонентов импортируются ТОЛЬКО в `custom-bootstrap.scss`: + +```scss +// Чат компоненты (в подпапке chat/) +@import "components/chat/message-inputs"; +@import "components/chat/chat-detail"; +@import "components/chat/chat-date-groups"; +@import "components/chat/chat-list-enhanced"; +@import "components/chat/chat-enhanced"; +@import "components/chat/chat-polls"; +@import "components/chat/chat-new-messages-indicator"; +@import "components/chat/message-selection"; +@import "components/chat/message-context-menu"; +@import "components/chat/message-reactions"; +``` + +**Последствия:** +- ❌ Chat стили попадают в `bootstrap-custom.css` (301 KB) +- ❌ Нелогично - chat не относится к Bootstrap +- ❌ Сложнее поддерживать - непонятно где искать + +#### 3. Устаревший TODO комментарий + +В `custom-bootstrap.scss` строки 197-199: +```scss +// Остальные компоненты (загружаются напрямую из CSS - не мигрировано) +// TODO: Мигрировать buttons, card-list, document-list, request-list, +// ios-search, feed-specific, base-app, request-detail +``` + +**Проблема:** Эти компоненты УЖЕ мигрированы и импортируются в `main.scss`! + +### 🟡 Средние проблемы + +#### 4. Пустая папка utilities/ + +``` +scss/utilities/ - ПУСТО +``` + +В `main.scss` закомментированы импорты: +```scss +// @import 'utilities/spacing-utils'; +// @import 'utilities/index'; +``` + +**Проблема:** +- Папка существует но не используется +- spacing-utils лежит в components/, а не utilities/ +- Запутанная структура + +#### 5. Нет главного README.md в scss/ + +**Отсутствует:** `backend/static/scss/README.md` + +Есть только: +- `components/BUTTONS_README.md` - документация одного компонента +- Нет общего описания архитектуры + +#### 6. Layout компоненты в custom-bootstrap.scss + +```scss +// Layout +@import "layout/chat-layout"; +``` + +И в main.scss: +```scss +@import 'layout/base-app'; +@import 'layout/chat-layout'; // ДУБЛЬ! +``` + +**chat-layout импортируется дважды!** + +### 🟢 Мелкие замечания + +#### 7. Несогласованность комментариев + +В `main.scss`: +```scss +// 4. UTILITIES - Вспомогательные классы +// @import 'utilities/spacing-utils'; +// @import 'utilities/index'; +``` + +Но `_spacing-utils.scss` находится в `components/`, не в `utilities/`! + +#### 8. Размер custom-bootstrap.scss + +202 строки, из них: +- ~70 строк - переопределения переменных Bootstrap +- ~30 строк - импорты компонентов (многие дублируются) +- ~100 строк - комментарии + +Файл становится перегруженным. + +## 📈 Статистика + +### Размеры +- **Всего SCSS файлов:** 51 +- **Всего строк SCSS:** 11,394 +- **Компонентов в components/:** 30 +- **Компонентов в components/chat/:** 10 +- **Layout файлов:** 2 +- **Abstracts файлов:** 4 + +### Импорты +- **Импортов в main.scss:** 30 +- **Импортов в custom-bootstrap.scss:** 25 +- **Дублированных импортов:** 8 компонентов + +### Compiled размеры +- **app.css:** 95 KB +- **bootstrap-custom.css:** 301 KB +- **Дубликаты в обоих:** ~15-20 KB + +## 🎯 Рекомендации по улучшению + +### Приоритет 1: Устранить дублирование (CRITICAL) + +#### Решение A: Разделить компоненты по назначению + +**app.css** (main.scss): +- Все application-specific компоненты +- Layout (base-app, chat-layout) +- Components (buttons, cards, modals, etc.) +- Chat components +- Spacing utilities + +**bootstrap-custom.css** (custom-bootstrap.scss): +- ТОЛЬКО переопределения Bootstrap переменных +- ТОЛЬКО импорт Bootstrap +- CSS variables (abstracts/css-variables) +- Убрать ВСЕ импорты компонентов + +#### Решение B: Создать отдельный chat.css + +**chat.css** (новый entry point): +- Все chat компоненты +- Chat layout +- Подключать только на страницах чата + +### Приоритет 2: Очистить custom-bootstrap.scss + +**Удалить из custom-bootstrap.scss:** +```scss +// ❌ УДАЛИТЬ ВСЕ ЭТО: +@import "layout/chat-layout"; +@import "components/common"; +@import "components/navbar"; +@import "components/sidebar"; +@import "components/page-header"; +@import "components/cards"; +@import "components/badges-bem"; +@import "components/chat/..."; // все 10 импортов +@import "components/comments"; +@import "components/modals"; +``` + +**Оставить только:** +```scss +// 1. Переопределение Bootstrap переменных +$spacer: 1rem; +$spacers: (...); +// и т.д. + +// 2. Импорт наших abstracts +@import "abstracts/variables"; +@import "abstracts/mixins"; + +// 3. Импорт Bootstrap +@import "../node_modules/bootstrap/scss/bootstrap"; + +// 4. CSS переменные +@import "abstracts/css-variables"; + +// ВСЕ! Никаких компонентов! +``` + +### Приоритет 3: Переместить spacing-utils + +```bash +# Переместить из components/ в utilities/ +mv scss/components/_spacing-utils.scss scss/utilities/ +mv scss/components/_layout-spacing.scss scss/utilities/ +``` + +Раскомментировать в main.scss: +```scss +// 4. UTILITIES - Вспомогательные классы +@import 'utilities/layout-spacing'; +@import 'utilities/spacing-utils'; +``` + +### Приоритет 4: Документация + +Создать `scss/README.md`: +```markdown +# SCSS Architecture + +## Structure +- abstracts/ - Variables, mixins, functions +- layout/ - Page layouts +- components/ - Reusable components +- utilities/ - Utility classes + +## Build +- main.scss → app.css (application styles) +- bootstrap-custom.scss → bootstrap-custom.css (Bootstrap only) + +## Components +[список всех компонентов] +``` + +### Приоритет 5: Удалить устаревшие TODO + +Удалить из custom-bootstrap.scss: +```scss +// Остальные компоненты (загружаются напрямую из CSS - не мигрировано) +// TODO: Мигрировать buttons, card-list, document-list, request-list, +// ios-search, feed-specific, base-app, request-detail +``` + +Все эти компоненты УЖЕ в main.scss! + +## 📋 План действий (по шагам) + +### Шаг 1: Backup текущего состояния +```bash +cp scss/custom-bootstrap.scss scss/custom-bootstrap.scss.backup +cp scss/main.scss scss/main.scss.backup +``` + +### Шаг 2: Очистить custom-bootstrap.scss +Удалить все импорты компонентов, оставить только: +- Переопределения переменных Bootstrap +- Импорт abstracts +- Импорт Bootstrap +- Импорт css-variables + +### Шаг 3: Переместить chat в main.scss +Добавить в main.scss все chat компоненты: +```scss +// Chat компоненты +@import 'components/chat/message-inputs'; +@import 'components/chat/chat-detail'; +// ... все 10 компонентов +``` + +### Шаг 4: Переместить spacing в utilities +```bash +mv scss/components/_spacing-utils.scss scss/utilities/ +mv scss/components/_layout-spacing.scss scss/utilities/ +``` + +### Шаг 5: Обновить импорты в main.scss +```scss +// Utilities +@import 'utilities/layout-spacing'; +@import 'utilities/spacing-utils'; +``` + +### Шаг 6: Пересобрать CSS +```bash +npm run build:app +npm run build:bootstrap +``` + +### Шаг 7: Проверить размеры +```bash +ls -lh css/app.css css/bootstrap-custom.css +``` + +**Ожидаемый результат:** +- app.css: увеличится на ~15-20 KB (добавятся chat компоненты) +- bootstrap-custom.css: уменьшится на ~15-20 KB (удалятся дубли) + +### Шаг 8: Создать документацию +Создать `scss/README.md` с описанием архитектуры + +### Шаг 9: Тестирование +1. Проверить главную страницу +2. Проверить страницы с чатом +3. Проверить все компоненты + +## 📊 Ожидаемые улучшения + +После рефакторинга: + +### До: +``` +app.css: 95 KB (без chat, с дублями) +bootstrap-custom.css: 301 KB (с chat, с дублями) +Дублирование: ~15-20 KB +``` + +### После: +``` +app.css: ~110 KB (с chat, без дублей) +bootstrap-custom.css: ~280 KB (только Bootstrap, без дублей) +Дублирование: 0 KB +Экономия: ~6-7% от общего размера +``` + +### Преимущества: +- ✅ Нет дубликатов кода +- ✅ Чистое разделение ответственности +- ✅ Логичная структура +- ✅ Легче поддерживать +- ✅ Меньший итоговый размер + +## 🎓 Best Practices нарушения + +### ❌ Текущие нарушения: + +1. **DRY (Don't Repeat Yourself)** - дублирование 8 компонентов +2. **Separation of Concerns** - chat компоненты в bootstrap файле +3. **Single Responsibility** - custom-bootstrap делает слишком много +4. **Clear Architecture** - utilities/ пуста, spacing в components/ + +### ✅ После рефакторинга: + +1. **DRY** - каждый компонент импортируется один раз +2. **Separation of Concerns** - четкое разделение: Bootstrap отдельно, app отдельно +3. **Single Responsibility** - каждый файл имеет одну четкую цель +4. **Clear Architecture** - логичная структура папок + +## 💡 Дополнительные рекомендации + +### 1. Рассмотреть переход на @use/@forward +SCSS @import deprecated, современный подход: +```scss +@use 'abstracts/variables' as vars; +@use 'components/buttons'; +``` + +### 2. Создать index.scss в папках +```scss +// components/chat/_index.scss +@forward 'message-inputs'; +@forward 'chat-detail'; +// ... + +// Использование: +@import 'components/chat'; // импортирует все +``` + +### 3. Добавить linter +```bash +npm install -D stylelint stylelint-config-sass-guidelines +``` + +### 4. Создать style guide +Документировать: +- Naming conventions +- File organization +- Import order +- Variables usage + +## 🏁 Заключение + +**Текущая архитектура:** 7/10 - Хорошая база, но есть критические проблемы + +**Основные проблемы:** +1. Дублирование импортов (CRITICAL) +2. Chat компоненты в bootstrap-custom (HIGH) +3. Неиспользуемая utilities/ папка (MEDIUM) + +**Действия:** +1. Срочно устранить дублирование +2. Переорганизовать структуру +3. Создать документацию + +**После рефакторинга ожидается:** 9/10 - Чистая, поддерживаемая архитектура + +--- + +**Дата:** 15 января 2026 +**Статус:** Требуется рефакторинг +**Приоритет:** HIGH diff --git a/docs/reports/SCSS_REORGANIZATION_PLAN.md b/docs/reports/SCSS_REORGANIZATION_PLAN.md new file mode 100644 index 00000000..1db57c6f --- /dev/null +++ b/docs/reports/SCSS_REORGANIZATION_PLAN.md @@ -0,0 +1,1412 @@ +# План реорганизации SCSS архитектуры + +**Дата:** 15 января 2026 +**Статус:** 📋 План +**Приоритет:** HIGH +**Время выполнения:** 6-8 часов (с Bootstrap) / 40-60 часов (без Bootstrap) + +## 🎯 Цель + +Преобразовать хаотичную структуру в чистую, поддерживаемую архитектуру с четкой категоризацией и переиспользуемыми компонентами. + +### ⚠️ КРИТИЧЕСКОЕ РЕШЕНИЕ: Bootstrap или нет? + +**Пользователь запросил:** Полный отказ от Bootstrap в пользу собственных стилей. + +**Это означает:** +- ❌ Удалить зависимость от Bootstrap (~301 KB CSS) +- ✅ Написать свою систему: Grid, Buttons, Forms, Modals, Cards, Utilities +- ⏱️ Увеличение времени реализации с 8 часов до **40-60 часов** +- 🎨 Полный контроль над кодом и стилями +- 📦 Уменьшение итогового размера бандла +- 🔧 Необходимость поддержки своего UI Kit + +**См. раздел "БОНУСНЫЙ ЭТАП: Отказ от Bootstrap" в конце документа.** + +--- + +## 📊 Целевая архитектура + +### Feature-based структура (подходит под Django структуру): + +``` +scss/ +├── abstracts/ # ✅ Оставить как есть +│ ├── _variables.scss # SCSS переменные +│ ├── _css-variables.scss # CSS custom properties +│ ├── _mixins.scss # Миксины +│ └── _index.scss # Экспорт +│ +├── core/ # ⭐ НОВАЯ ПАПКА - UI Kit +│ ├── _buttons.scss # Кнопки +│ ├── _forms.scss # Формы и инпуты +│ ├── _badges.scss # Один badge компонент (объединить 2) +│ ├── _cards.scss # Карточки +│ ├── _lists.scss # ОДИН базовый список +│ ├── _modals.scss # ОДИН modal (объединить 2) +│ ├── _avatars.scss # Аватары +│ ├── _search.scss # Поиск (вместо ios-search) +│ └── _comments.scss # Комментарии +│ +├── layout/ # Структура приложения +│ ├── _grid.scss # App grid (из base-app.scss) +│ ├── _header.scss # Navbar + page-header (объединить) +│ ├── _sidebar.scss # ✅ Оставить +│ └── _rightbar.scss # Rightbar + calendar (объединить) +│ +├── features/ # ⭐ НОВАЯ ПАПКА - Feature-specific +│ ├── feed/ +│ │ ├── _layout.scss # Feed layout +│ │ ├── _post-card.scss # Карточка поста +│ │ └── _post-form.scss # ✅ Переместить +│ ├── requests/ +│ │ ├── _list.scss # Список заявок +│ │ ├── _detail.scss # Детали заявки +│ │ └── _picker.scss # Recipient picker +│ ├── documents/ +│ │ └── _list.scss # Список документов +│ ├── employees/ +│ │ ├── _controls.scss # Department controls +│ │ └── _team-viz.scss # Team visualization (team-wheel) +│ ├── notifications/ +│ │ └── _center.scss # Notification center +│ └── chat/ # ✅ Оставить подпапку +│ └── ... # 10 файлов как есть +│ +└── utilities/ # Utility классы + └── _spacing.scss # Объединить spacing-utils + layout-spacing +``` + +--- + +## 📋 Этапы выполнения + +### **Этап 0: Подготовка (30 мин)** + +#### 0.1 Создать backup +```bash +cd backend/static +cp -r scss scss_backup_$(date +%Y%m%d) +git add -A +git commit -m "backup: SCSS before reorganization" +``` + +#### 0.2 Создать новые папки +```bash +cd scss +mkdir -p core features utilities +mkdir -p features/{feed,requests,documents,employees,notifications} +``` + +#### 0.3 Создать tracking файл +```bash +# Создать файл для отслеживания прогресса +touch REORGANIZATION_PROGRESS.md +``` + +--- + +### **Этап 1: Аудит и группировка (1 час)** + +#### 1.1 Создать матрицу миграции + +Создать `MIGRATION_MATRIX.md`: + +```markdown +| Старый файл | Новое расположение | Статус | Требуется слияние | +|-------------|-------------------|--------|-------------------| +| _buttons.scss | core/_buttons.scss | ⏳ | Нет | +| _badges-bem.scss | core/_badges.scss | ⏳ | Да (с telegram) | +| _badges-telegram-style.scss | core/_badges.scss | ⏳ | Да (с bem) | +| _ios-search.scss | core/_search.scss | ⏳ | Нет | +| ... | ... | ... | ... | +``` + +#### 1.2 Проанализировать зависимости +```bash +# Найти все импорты +grep -r "@import" main.scss custom-bootstrap.scss + +# Найти использование классов в шаблонах +grep -r "class=" templates/ | grep -E "(ios-|badge-|card-list)" +``` + +--- + +### **Этап 2: Core UI Kit (2 часа)** + +#### 2.1 Простые перемещения (без изменений) + +```bash +# Buttons - просто переместить +mv components/_buttons.scss core/_buttons.scss + +# Cards - переместить +mv components/_cards.scss core/_cards.scss + +# Comments - переместить +mv components/_comments.scss core/_comments.scss + +# Common - переместить +mv components/_common.scss core/_common.scss +``` + +#### 2.2 Переименование файлов + +```bash +# iOS-search → search +mv components/_ios-search.scss core/_search.scss + +# iOS-components разобрать на части или переименовать +mv components/_ios-components.scss core/_ui-elements.scss +``` + +**Задача:** Открыть `_ios-components.scss`, посмотреть что там, возможно разбить на несколько файлов. + +#### 2.3 Объединение badges (ВАЖНО!) + +**Шаги:** +1. Открыть `_badges-bem.scss` и `_badges-telegram-style.scss` +2. Проанализировать что можно объединить +3. Создать единый `core/_badges.scss`: + +```scss +// core/_badges.scss + +// ===== BASE BADGE ===== +.badge { + // Общие стили +} + +// ===== BEM VARIANT ===== +.badge--bem { + // BEM специфичные стили +} + +// ===== TELEGRAM VARIANT ===== +.badge--telegram { + // Telegram специфичные стили +} + +// ===== STATUS VARIANTS ===== +.badge--success { } +.badge--danger { } +.badge--pending { } +``` + +4. Удалить старые файлы +5. Обновить импорты + +#### 2.4 Объединение modals + +```bash +# Объединить modal-overrides в modals +cat components/_modal-overrides.scss >> components/_modals.scss +mv components/_modals.scss core/_modals.scss +rm components/_modal-overrides.scss +``` + +**Почистить дубли** в объединенном файле! + +#### 2.5 Создать core/_lists.scss + +Проанализировать: +- `_card-list.scss` - общие стили списков +- `_list-view.scss` - view стили + +Объединить общее в `core/_lists.scss`: + +```scss +// core/_lists.scss + +// ===== BASE LIST ===== +.list { + // Базовые стили списка +} + +// ===== LIST ITEM ===== +.list__item { + // Стили элемента списка +} + +// ===== VARIANTS ===== +.list--compact { } +.list--cards { } +``` + +--- + +### **Этап 3: Layout реорганизация (1.5 часа)** + +#### 3.1 Создать layout/_grid.scss + +```bash +# Переместить base-app +mv layout/_base-app.scss layout/_grid.scss +``` + +Внутри файла переименовать классы: +- `.app-layout` → `.app-grid` +- Улучшить документацию + +#### 3.2 Объединить header компоненты + +**Анализ:** +- `_navbar.scss` - навигация +- `_page-header.scss` - заголовок страницы + +**Варианты:** + +**A) Объединить в один файл:** +```bash +cat components/_navbar.scss components/_page-header.scss > layout/_header.scss +``` + +**B) Оставить раздельно но переименовать:** +```bash +mv components/_navbar.scss layout/_navbar.scss +mv components/_page-header.scss layout/_page-header.scss +``` + +**Рекомендация:** Вариант B (раздельно), но добавить комментарий что это части header'а. + +#### 3.3 Rightbar + Calendar + +```bash +# Переместить rightbar-calendar +mv components/_rightbar-calendar.scss layout/_rightbar.scss +``` + +Внутри переименовать классы: +- `.rightbar-card` → `.rightbar__card` +- `.calendar-wrap` → `.rightbar__calendar` + +--- + +### **Этап 4: Features организация (2 часа)** + +#### 4.1 Feed feature + +```bash +# Создать feed папку +mkdir -p features/feed + +# Переместить компоненты +mv components/_feed-specific.scss features/feed/_layout.scss +mv components/_post-form.scss features/feed/_post-form.scss +mv components/_join-scroller.scss features/feed/_members-scroller.scss + +# Если есть feed-header или подобное - тоже сюда +``` + +Создать `features/feed/_index.scss`: +```scss +@forward 'layout'; +@forward 'post-form'; +@forward 'members-scroller'; +``` + +#### 4.2 Requests feature + +```bash +mkdir -p features/requests + +mv components/_request-list.scss features/requests/_list.scss +mv components/_request-detail.scss features/requests/_detail.scss +mv components/_recipientPicker.scss features/requests/_recipient-picker.scss +``` + +Создать `features/requests/_index.scss`: +```scss +@forward 'list'; +@forward 'detail'; +@forward 'recipient-picker'; +``` + +#### 4.3 Documents feature + +```bash +mkdir -p features/documents + +mv components/_document-list.scss features/documents/_list.scss +``` + +Создать `features/documents/_index.scss`: +```scss +@forward 'list'; +``` + +#### 4.4 Employees feature + +```bash +mkdir -p features/employees + +mv components/_department-controls.scss features/employees/_controls.scss +mv components/_team-wheel.scss features/employees/_team-viz.scss +``` + +Переименовать внутри `_team-viz.scss`: +- `.team-wheel` → `.team-viz` +- Обновить комментарии + +Создать `features/employees/_index.scss`: +```scss +@forward 'controls'; +@forward 'team-viz'; +``` + +#### 4.5 Notifications feature + +```bash +mkdir -p features/notifications + +mv components/_notifications.scss features/notifications/_center.scss +``` + +Создать `features/notifications/_index.scss`: +```scss +@forward 'center'; +``` + +#### 4.6 Chat feature (оставить как есть) + +```bash +# Chat уже в подпапке - оставить +# features/chat/ уже существует как components/chat/ +# Просто переместить папку целиком +mv components/chat features/chat +``` + +#### 4.7 Logout modal (куда деть?) + +**Варианты:** +1. В `features/auth/_logout-modal.scss` +2. В `core/_modals.scss` (добавить специфичные стили) +3. Удалить если можно использовать общий modal + +**Рекомендация:** Вариант 2 - добавить `.modal--logout` в core модалы. + +```bash +# Слить с core/modals +cat components/_logout-modal.scss >> core/_modals.scss +rm components/_logout-modal.scss +``` + +--- + +### **Этап 5: Utilities (30 мин)** + +#### 5.1 Объединить spacing + +```bash +# Объединить два spacing файла +cat components/_layout-spacing.scss components/_spacing-utils.scss > utilities/_spacing.scss + +# Почистить дубли в объединенном файле +# Удалить старые +rm components/_layout-spacing.scss +rm components/_spacing-utils.scss +``` + +#### 5.2 Проверить пустую utilities папку + +```bash +# Удалить старую пустую папку если осталась +rmdir utilities/ 2>/dev/null || true +``` + +--- + +### **Этап 6: Обновление импортов (1.5 часа)** + +#### 6.1 Создать новый main.scss + +Создать `main.scss.new`: + +```scss +// ============================================================================= +// Main Application SCSS +// ============================================================================= +// Главный файл для сборки всех стилей приложения (кроме Bootstrap) +// Компилируется в css/app.css + +// ============================================================================= +// 1. ABSTRACTS - Переменные, миксины, функции +// ============================================================================= +@import 'abstracts/index'; + +// ============================================================================= +// 2. CORE - UI Kit компоненты +// ============================================================================= +@import 'core/buttons'; +@import 'core/forms'; +@import 'core/badges'; +@import 'core/cards'; +@import 'core/lists'; +@import 'core/modals'; +@import 'core/avatars'; +@import 'core/search'; +@import 'core/comments'; +@import 'core/common'; + +// ============================================================================= +// 3. LAYOUT - Структура приложения +// ============================================================================= +@import 'layout/grid'; +@import 'layout/navbar'; +@import 'layout/page-header'; +@import 'layout/sidebar'; +@import 'layout/rightbar'; + +// ============================================================================= +// 4. UTILITIES - Вспомогательные классы +// ============================================================================= +@import 'utilities/spacing'; + +// ============================================================================= +// 5. FEATURES - Feature-specific стили +// ============================================================================= + +// Feed +@import 'features/feed/layout'; +@import 'features/feed/post-form'; +@import 'features/feed/members-scroller'; + +// Requests +@import 'features/requests/list'; +@import 'features/requests/detail'; +@import 'features/requests/recipient-picker'; + +// Documents +@import 'features/documents/list'; + +// Employees +@import 'features/employees/controls'; +@import 'features/employees/team-viz'; + +// Notifications +@import 'features/notifications/center'; + +// Chat +@import 'features/chat/message-inputs'; +@import 'features/chat/chat-detail'; +@import 'features/chat/chat-date-groups'; +@import 'features/chat/chat-list-enhanced'; +@import 'features/chat/chat-enhanced'; +@import 'features/chat/chat-polls'; +@import 'features/chat/chat-new-messages-indicator'; +@import 'features/chat/message-selection'; +@import 'features/chat/message-context-menu'; +@import 'features/chat/message-reactions'; + +// ============================================================================= +// ПРИМЕЧАНИЯ +// ============================================================================= +// - Bootstrap подключается отдельно через bootstrap-custom.css +// - CSS переменные (--variable) определены в abstracts/_css-variables.scss +// - SCSS переменные ($variable) определены в abstracts/_variables.scss +// - Порядок импортов критичен для CSS каскада +// - Core компоненты = переиспользуемые UI элементы +// - Features = специфичные для конкретных функций приложения +``` + +#### 6.2 Очистить custom-bootstrap.scss + +Создать `custom-bootstrap.scss.new`: + +```scss +// ============================================================================= +// Custom Bootstrap Configuration +// ============================================================================= +// Этот файл переопределяет переменные Bootstrap ДО его компиляции + +// ============================================================================= +// 1. ПЕРЕОПРЕДЕЛЕНИЕ BOOTSTRAP ПЕРЕМЕННЫХ +// ============================================================================= + +// Система отступов (8px grid) +$spacer: 1rem !default; +$spacers: ( + 0: 0, + 1: 0.25rem, // 4px + 2: 0.5rem, // 8px + 3: 0.75rem, // 12px + 4: 1rem, // 16px + 5: 1.5rem, // 24px +) !default; + +// Grid +$grid-gutter-width: 0.5rem !default; + +// Cards +$card-spacer-y: 1rem !default; +$card-spacer-x: 1rem !default; +$card-border-radius: 14px !default; + +// Buttons +$btn-padding-y: 0.5rem !default; +$btn-padding-x: 1rem !default; + +// Inputs +$input-padding-y: 0.5rem !default; +$input-padding-x: 1rem !default; + +// ============================================================================= +// 2. ИМПОРТ ABSTRACTS +// ============================================================================= +@import "abstracts/variables"; +@import "abstracts/mixins"; + +// ============================================================================= +// 3. ИМПОРТ BOOTSTRAP +// ============================================================================= +@import "../node_modules/bootstrap/scss/bootstrap"; + +// ============================================================================= +// 4. CSS ПЕРЕМЕННЫЕ (RUNTIME) +// ============================================================================= +@import "abstracts/css-variables"; + +// ============================================================================= +// ВСЕ! Никаких компонентов здесь! +// Все компоненты в main.scss → app.css +// ============================================================================= +``` + +#### 6.3 Тестовая компиляция + +```bash +cd backend/static + +# Backup старых файлов +mv scss/main.scss scss/main.scss.old +mv scss/custom-bootstrap.scss scss/custom-bootstrap.scss.old + +# Применить новые +mv scss/main.scss.new scss/main.scss +mv scss/custom-bootstrap.scss.new scss/custom-bootstrap.scss + +# Попробовать собрать +npm run build:app 2>&1 | tee build_errors.log + +# Если ошибки - смотреть build_errors.log +``` + +#### 6.4 Исправление ошибок импортов + +```bash +# Найти все missing imports +grep -i "can't find" build_errors.log + +# Найти старые пути в файлах +grep -r "@import.*components/" scss/ + +# Исправить пути +``` + +--- + +### **Этап 7: Cleanup (30 мин)** + +#### 7.1 Удалить старую components папку + +```bash +# Проверить что components пуста +ls -la scss/components/ + +# Если пусто - удалить +rm -rf scss/components/ + +# Оставить только .gitkeep или удалить совсем +``` + +#### 7.2 Удалить BUTTONS_README.md из старого места + +```bash +# Переместить в новое место +mv scss/components/BUTTONS_README.md docs/guides/BUTTONS_COMPONENT.md + +# Или в core папку +mv scss/components/BUTTONS_README.md scss/core/README_BUTTONS.md +``` + +#### 7.3 Проверить дубликаты + +```bash +# Найти дублирующиеся селекторы +grep -r "^\.badge" scss/ | sort | uniq -d + +# Найти дублирующиеся @import +grep -r "@import" scss/main.scss scss/custom-bootstrap.scss | sort | uniq -d +``` + +--- + +### **Этап 8: Тестирование (1 час)** + +#### 8.1 Сборка финальных CSS + +```bash +# Собрать все +npm run build:app +npm run build:bootstrap + +# Проверить размеры +ls -lh css/app.css css/bootstrap-custom.css + +# Сравнить с backup +ls -lh css_backup/app.css css_backup/bootstrap-custom.css +``` + +#### 8.2 Визуальное тестирование + +**Проверить страницы:** +1. Главная / feed +2. Список заявок +3. Детали заявки +4. Список документов +5. Список сотрудников +6. Уведомления +7. Чат +8. Logout modal +9. Search +10. Forms + +**Чек-лист для каждой страницы:** +- [ ] Стили загружаются +- [ ] Нет визуальных багов +- [ ] Кнопки работают +- [ ] Формы выглядят нормально +- [ ] Responsive работает + +#### 8.3 Проверка консоли браузера + +```javascript +// В DevTools Console +// Проверить нет ли 404 на CSS +// Проверить размер загруженных CSS +performance.getEntriesByType('resource') + .filter(r => r.name.includes('.css')) +``` + +--- + +### **Этап 9: Документация (30 мин)** + +#### 9.1 Создать scss/README.md + +```markdown +# SCSS Architecture + +## Structure + +### Core (UI Kit) +Переиспользуемые UI компоненты: buttons, cards, badges, modals, etc. + +### Layout +Структура приложения: grid, header, sidebar, rightbar. + +### Features +Feature-specific стили, организованные по функциям приложения. + +### Utilities +Utility классы: spacing, helpers, etc. + +## Build +`npm run build:app` - компилирует main.scss → app.css +`npm run build:bootstrap` - компилирует bootstrap-custom.scss → bootstrap-custom.css + +## Adding New Components +1. Core component → `core/_component.scss` +2. Feature component → `features/feature/_component.scss` +3. Add import to `main.scss` +4. Run `npm run build:app` +``` + +#### 9.2 Обновить docs/guides/SCSS_QUICK_REFERENCE.md + +Обновить пути и структуру в quick reference. + +#### 9.3 Создать отчет о реорганизации + +`docs/reports/SCSS_REORGANIZATION_COMPLETE.md` с: +- Что было изменено +- Новая структура +- Метрики (файлы, размеры, время) + +--- + +### **Этап 10: Git commit (15 мин)** + +```bash +# Добавить все изменения +git add -A + +# Коммит +git commit -m "refactor: Complete SCSS architecture reorganization + +- Restructured 51 SCSS files into logical categories +- Created core/ (UI Kit), layout/, features/, utilities/ folders +- Merged duplicate components: badges (2→1), modals (2→1), spacing (2→1) +- Renamed files: ios-search→search, team-wheel→team-viz, etc. +- Cleaned custom-bootstrap.scss from component imports +- Moved chat components from bootstrap to main.scss +- Updated all imports in main.scss and custom-bootstrap.scss + +Structure: +- core/ (9 files) - Reusable UI components +- layout/ (5 files) - App structure +- features/ (16 files in 6 folders) - Feature-specific styles +- utilities/ (1 file) - Utility classes + +Benefits: +- Clear categorization +- No duplicate imports +- Logical feature grouping +- Easier maintenance +- Clean architecture + +Breaking changes: None (CSS class names unchanged) +" + +# Push +git push +``` + +--- + +## 📊 Метрики успеха + +### До реорганизации: +``` +components/ - 30 файлов (хаос) +layout/ - 2 файла +utilities/ - пуста +Дублирующиеся импорты: 8 компонентов +Странные названия: ios-, telegram-, -specific, team-wheel +``` + +### После реорганизации: +``` +core/ - 9-10 файлов (UI Kit) +layout/ - 4-5 файлов (структура) +features/ - 16 файлов в 6 папках (по функциям) +utilities/ - 1 файл (spacing) +Дублирующиеся импорты: 0 +Понятные названия: search, badges, team-viz +``` + +### Улучшения: +- ✅ Четкая категоризация +- ✅ Нет дубликатов +- ✅ Логичная группировка +- ✅ Легче найти нужный файл +- ✅ Понятные названия +- ✅ Масштабируемая структура + +--- + +## ⚠️ Риски и как их минимизировать + +### Риск 1: Сломать стили +**Минимизация:** +- Делать backup перед каждым этапом +- Тестировать после каждого этапа +- Не менять CSS классы, только структуру файлов +- Использовать git для отката + +### Риск 2: Забыть обновить импорт +**Минимизация:** +- Вести матрицу миграции +- Проверять build errors +- Grep поиск старых путей +- Тестовая компиляция после каждого шага + +### Риск 3: Потерять изменения +**Минимизация:** +- Git commit после каждого этапа +- Backup папка +- Документировать прогресс + +### Риск 4: Слишком долго +**Минимизация:** +- Разбить на этапы +- Можно делать частями (по 2-3 этапа в день) +- Приоритет: сначала core, потом features + +--- + +## 🎯 Альтернативный подход (если мало времени) + +### Минимальный рефакторинг (2 часа): + +1. **Убрать дубликаты из custom-bootstrap** (30 мин) +2. **Переместить chat в main.scss** (15 мин) +3. **Объединить badges** (20 мин) +4. **Объединить modals** (15 мин) +5. **Объединить spacing** (20 мин) +6. **Переименовать ios-* файлы** (20 мин) + +Остальное оставить как есть. Будет лучше но не идеально. + +--- + +## 📚 Дополнительные ресурсы + +- [Sass Guidelines](https://sass-guidelin.es/) - best practices +- [7-1 Pattern](https://sass-guidelin.es/#the-7-1-pattern) - архитектура +- [ITCSS](https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/) - методология +- [Atomic Design](https://atomicdesign.bradfrost.com/) - для вдохновения + +--- + +--- + +## 🚨 БОНУСНЫЙ ЭТАП: Полный отказ от Bootstrap + +**⚠️ ВНИМАНИЕ:** Этот этап увеличивает время реализации в 5-7 раз! + +### Зачем отказываться от Bootstrap? + +**Плюсы:** +- ✅ Полный контроль над кодом +- ✅ Уменьшение размера бандла (~300 KB → ~100 KB) +- ✅ Нет неиспользуемого кода +- ✅ Точная семантика под наше приложение +- ✅ Легче кастомизация +- ✅ Современный CSS (Grid, Custom Properties, Container Queries) + +**Минусы:** +- ❌ Нужно написать свой UI Kit с нуля +- ❌ Время: 40-60 часов вместо 8 часов +- ❌ Нужно поддерживать свой код +- ❌ Протестировать все компоненты +- ❌ Обеспечить кроссбраузерность +- ❌ Документировать систему + +### Что нужно переписать? + +**Bootstrap предоставляет (~180 компонентов):** + +1. **Grid System** - 12-колоночная сетка +2. **Typography** - заголовки, параграфы, списки +3. **Buttons** - 9 вариантов кнопок + sizes + states +4. **Forms** - inputs, selects, checkboxes, radio, validation +5. **Cards** - карточки с header/body/footer +6. **Modals** - модальные окна с backdrop +7. **Alerts** - уведомления +8. **Badges** - бейджи +9. **Breadcrumbs** - хлебные крошки +10. **Dropdowns** - выпадающие меню +11. **Navbar** - навигация +12. **Pagination** - пагинация +13. **Progress** - прогресс-бары +14. **Spinners** - загрузчики +15. **Tooltips** - подсказки +16. **Popovers** - всплывающие окна +17. **Utilities** - margin, padding, flex, display, colors... + +**Используется в проекте (по grep):** +```bash +# Найти что реально используется +grep -r "class=" templates/ | grep -oE '\.(btn|card|modal|form-|input-|alert|badge|nav|dropdown|pagination|container|row|col-|d-|m-|p-|text-|bg-|border-)' | sort | uniq -c +``` + +### Этап 0: Аудит использования Bootstrap (4 часа) + +#### 0.1 Собрать статистику использования + +```bash +cd backend + +# Создать скрипт для анализа +cat > analyze_bootstrap_usage.py << 'EOF' +import re +from pathlib import Path +from collections import Counter + +# Bootstrap классы +BS_PATTERNS = [ + r'\bbtn-\w+', r'\bcard-\w+', r'\bmodal-\w+', r'\bform-\w+', + r'\balert-\w+', r'\bbadge-\w+', r'\bnav-\w+', r'\bcol-\w+', + r'\brow\b', r'\bcontainer\b', r'\bd-\w+', r'\bm[tblrxy]?-\d', + r'\bp[tblrxy]?-\d', r'\btext-\w+', r'\bbg-\w+', r'\bborder-\w+' +] + +templates_dir = Path('templates') +all_classes = Counter() + +for html_file in templates_dir.rglob('*.html'): + content = html_file.read_text(encoding='utf-8') + for pattern in BS_PATTERNS: + matches = re.findall(pattern, content) + all_classes.update(matches) + +print("=== TOP 50 BOOTSTRAP CLASSES ===") +for cls, count in all_classes.most_common(50): + print(f"{cls:30} - {count:4} uses") + +print(f"\n=== TOTAL: {len(all_classes)} unique Bootstrap classes ===") +EOF + +.venv/Scripts/python analyze_bootstrap_usage.py > bootstrap_usage_report.txt +``` + +#### 0.2 Создать матрицу замены + +`BOOTSTRAP_REPLACEMENT_MATRIX.md`: + +```markdown +| Bootstrap класс | Использований | Наш класс | Статус | +|-----------------|---------------|-----------|--------| +| .btn-primary | 150 | .button--primary | ⏳ | +| .card | 120 | .card | ⏳ | +| .modal | 45 | .modal | ⏳ | +| .form-control | 200 | .input | ⏳ | +| .container | 80 | .container | ⏳ | +| .row | 100 | .grid | ⏳ | +| .col-* | 300 | .grid__col | ⏳ | +``` + +#### 0.3 Оценить объем работы + +```bash +# Посчитать templates с Bootstrap +find templates/ -name "*.html" -exec grep -l "btn\|card\|modal\|form-control" {} \; | wc -l + +# Посчитать строк кода +find templates/ -name "*.html" | xargs wc -l +``` + +### Этап B1: Создать свою Grid систему (6 часов) + +#### B1.1 Создать core/_grid.scss + +```scss +// core/_grid.scss + +// ============================================================================= +// GRID SYSTEM +// ============================================================================= +// Своя grid система на CSS Grid (не flexbox как Bootstrap) + +// Переменные +$grid-columns: 12; +$grid-gap: 1rem; +$grid-container-max: 1200px; + +// Container +.container { + width: 100%; + max-width: $grid-container-max; + margin: 0 auto; + padding: 0 1rem; +} + +.container--fluid { + max-width: 100%; +} + +// Grid +.grid { + display: grid; + grid-template-columns: repeat($grid-columns, 1fr); + gap: $grid-gap; + + // Responsive gaps + &--gap-sm { gap: 0.5rem; } + &--gap-lg { gap: 2rem; } +} + +// Grid columns +@for $i from 1 through $grid-columns { + .grid__col-#{$i} { + grid-column: span $i; + } +} + +// Responsive +@media (max-width: 768px) { + .grid__col-12-mobile { + grid-column: span 12; + } +} + +// Auto-fit grid +.grid--auto { + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); +} +``` + +#### B1.2 Создать миграционный скрипт + +```python +# scripts/replace_bootstrap_grid.py +import re +from pathlib import Path + +def replace_bootstrap_grid(content): + # .row → .grid + content = re.sub(r'\brow\b', 'grid', content) + + # .col-{n} → .grid__col-{n} + content = re.sub(r'\bcol-(\d+)', r'grid__col-\1', content) + + # .container → .container (оставить) + + return content + +templates_dir = Path('templates') +for html_file in templates_dir.rglob('*.html'): + content = html_file.read_text(encoding='utf-8') + new_content = replace_bootstrap_grid(content) + if content != new_content: + html_file.write_text(new_content, encoding='utf-8') + print(f"Updated: {html_file}") +``` + +### Этап B2: Создать свою Button систему (4 часа) + +```scss +// core/_buttons.scss + +.button { + // Base styles + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem 1rem; + border: none; + border-radius: 0.375rem; + font-size: 1rem; + font-weight: 500; + line-height: 1.5; + cursor: pointer; + transition: all 0.2s; + + &:hover { opacity: 0.85; } + &:active { transform: scale(0.98); } + &:disabled { opacity: 0.5; cursor: not-allowed; } + + // Variants + &--primary { background: var(--color-primary); color: white; } + &--secondary { background: var(--color-secondary); color: white; } + &--success { background: var(--color-success); color: white; } + &--danger { background: var(--color-danger); color: white; } + &--outline { background: transparent; border: 1px solid currentColor; } + + // Sizes + &--sm { padding: 0.25rem 0.5rem; font-size: 0.875rem; } + &--lg { padding: 0.75rem 1.5rem; font-size: 1.125rem; } + + // Full width + &--block { width: 100%; } +} + +// Миграция: .btn-primary → .button.button--primary +``` + +### Этап B3: Создать Form систему (8 часов) + +```scss +// core/_forms.scss + +.form-group { + margin-bottom: 1rem; +} + +.label { + display: block; + margin-bottom: 0.25rem; + font-weight: 500; + font-size: 0.875rem; +} + +.input, +.textarea, +.select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid var(--color-border); + border-radius: 0.375rem; + font-size: 1rem; + transition: border-color 0.2s; + + &:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb), 0.1); + } + + &--error { + border-color: var(--color-danger); + } + + &:disabled { + background: var(--color-gray-100); + cursor: not-allowed; + } +} + +.textarea { + resize: vertical; + min-height: 100px; +} + +.checkbox, +.radio { + // Кастомные checkbox/radio + appearance: none; + width: 1.25rem; + height: 1.25rem; + border: 2px solid var(--color-border); + border-radius: 0.25rem; + cursor: pointer; + + &:checked { + background: var(--color-primary); + border-color: var(--color-primary); + } +} + +.radio { + border-radius: 50%; +} +``` + +### Этап B4: Utilities система (4 часа) + +```scss +// utilities/_spacing.scss + +// Margin и Padding генератор +$spacers: ( + 0: 0, + 1: 0.25rem, + 2: 0.5rem, + 3: 0.75rem, + 4: 1rem, + 5: 1.5rem, + 6: 2rem, +); + +@each $key, $value in $spacers { + // Margin + .m-#{$key} { margin: $value; } + .mt-#{$key} { margin-top: $value; } + .mb-#{$key} { margin-bottom: $value; } + .ml-#{$key} { margin-left: $value; } + .mr-#{$key} { margin-right: $value; } + .mx-#{$key} { margin-left: $value; margin-right: $value; } + .my-#{$key} { margin-top: $value; margin-bottom: $value; } + + // Padding + .p-#{$key} { padding: $value; } + .pt-#{$key} { padding-top: $value; } + .pb-#{$key} { padding-bottom: $value; } + .pl-#{$key} { padding-left: $value; } + .pr-#{$key} { padding-right: $value; } + .px-#{$key} { padding-left: $value; padding-right: $value; } + .py-#{$key} { padding-top: $value; padding-bottom: $value; } +} + +// Display utilities +.d-none { display: none; } +.d-block { display: block; } +.d-flex { display: flex; } +.d-grid { display: grid; } +.d-inline { display: inline; } +.d-inline-block { display: inline-block; } + +// Flexbox utilities +.flex-row { flex-direction: row; } +.flex-column { flex-direction: column; } +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } +.justify-end { justify-content: flex-end; } +.align-center { align-items: center; } +.align-start { align-items: flex-start; } +.gap-1 { gap: 0.25rem; } +.gap-2 { gap: 0.5rem; } +.gap-3 { gap: 1rem; } + +// Text utilities +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } +.text-bold { font-weight: bold; } +.text-sm { font-size: 0.875rem; } +.text-lg { font-size: 1.125rem; } +``` + +### Этап B5: Cards, Modals, Alerts (6 часов) + +Написать свои версии этих компонентов. + +### Этап B6: Массовая замена в templates (12 часов) + +```python +# scripts/migrate_all_bootstrap_classes.py + +REPLACEMENTS = { + # Buttons + r'\bbtn\b': 'button', + r'\bbtn-primary\b': 'button--primary', + r'\bbtn-secondary\b': 'button--secondary', + r'\bbtn-sm\b': 'button--sm', + r'\bbtn-lg\b': 'button--lg', + + # Forms + r'\bform-control\b': 'input', + r'\bform-group\b': 'form-group', + r'\bform-label\b': 'label', + + # Grid + r'\brow\b': 'grid', + r'\bcol-(\d+)': r'grid__col-\1', + + # Cards + r'\bcard\b': 'card', + r'\bcard-body\b': 'card__body', + r'\bcard-header\b': 'card__header', + + # Utilities + r'\bd-flex\b': 'd-flex', + r'\bjustify-content-center\b': 'justify-center', + # ... еще сотни замен +} + +def migrate_template(filepath): + content = filepath.read_text(encoding='utf-8') + for old, new in REPLACEMENTS.items(): + content = re.sub(old, new, content) + filepath.write_text(content, encoding='utf-8') + +# Запустить на всех templates +for html_file in Path('templates').rglob('*.html'): + migrate_template(html_file) +``` + +### Этап B7: Удаление Bootstrap (1 час) + +```bash +# Удалить из package.json +npm uninstall bootstrap + +# Удалить custom-bootstrap.scss +rm scss/custom-bootstrap.scss + +# Удалить из templates +grep -r "bootstrap-custom.css" templates/ +# Заменить на только app.css + +# Обновить build скрипты +# Оставить только npm run build:app +``` + +### Этап B8: Тестирование (8 часов) + +Протестировать каждую страницу: +- Визуальная регрессия +- Responsive +- Кроссбраузерность +- Формы работают +- Модалы открываются +- И т.д. + +--- + +## 📊 Сравнение подходов + +### Вариант А: С Bootstrap (рекомендуется сейчас) + +**Плюсы:** +- ⏱️ Быстро: 6-8 часов +- ✅ Проверенное решение +- ✅ Хорошая документация +- ✅ Обновления безопасности +- ✅ Кроссбраузерность из коробки + +**Минусы:** +- 📦 Размер бандла: ~300 KB +- 🔧 Ограничения кастомизации +- 📚 Неиспользуемый код + +**Итоговый размер:** +- bootstrap-custom.css: ~280 KB +- app.css: ~95 KB +- **ИТОГО: ~375 KB** + +### Вариант Б: Без Bootstrap (требует времени) + +**Плюсы:** +- 📦 Размер бандла: ~100 KB (в 3 раза меньше!) +- 🎨 Полный контроль +- 🚀 Только нужный код +- 💅 Современный CSS + +**Минусы:** +- ⏱️ Долго: 40-60 часов +- 🔧 Нужна поддержка +- 🐛 Больше багов +- 📚 Нужна документация + +**Итоговый размер:** +- app.css: ~100 KB +- **ИТОГО: ~100 KB** + +--- + +## 🎯 Рекомендуемая стратегия + +### Поэтапный отказ от Bootstrap: + +1. **Этап 1-10 (6-8 часов):** Реорганизация SCSS с Bootstrap +2. **Использовать проект 2-3 месяца** +3. **Этап B (40-60 часов):** Постепенная замена Bootstrap своими стилями + - Сначала Grid → готово за выходные + - Потом Buttons → неделя + - Потом Forms → неделя + - Постепенно все остальное +4. **Тестирование на продакшене** +5. **Удаление Bootstrap когда все заменено** + +**Преимущества поэтапного подхода:** +- ✅ Не ломаем текущий проект +- ✅ Можно тестировать части +- ✅ Откатиться легко +- ✅ Меньше риска + +--- + +## 🤔 Мое мнение + +**Для корпоративного Django приложения:** + +👍 **ЗА отказ от Bootstrap:** +- Полный контроль над кодом +- Меньше размер +- Современные CSS возможности +- Нет jQuery зависимостей (если используете Bootstrap JS) + +👎 **ПРОТИВ отказа от Bootstrap:** +- Огромная работа (40-60 часов) +- Нужно поддерживать свой код +- Риск багов +- Уже работает хорошо + +**Мой совет:** +1. Сейчас сделать реорганизацию С Bootstrap (8 часов) +2. Использовать 1-2 месяца +3. Если реально чувствуете ограничения - начать постепенно заменять +4. Если все работает нормально - оставить Bootstrap + +**Большинство крупных проектов используют Bootstrap/Tailwind/Foundation** - это нормально и правильно. + +--- + +**Готов начать? Какой вариант выбираем?** + +**A)** Реорганизация с Bootstrap (8 часов, безопасно) +**Б)** Полный отказ от Bootstrap (60 часов, амбициозно) +**В)** Поэтапная замена (8 часов сейчас + 40 часов потом)